zoukankan      html  css  js  c++  java
  • [OAuth]基于DotNetOpenAuth实现Client Credentials Grant

    Client Credentials Grant是指直接由Client向Authorization Server请求access token,无需用户(Resource Owner)的授权。比如我们提供OpenAPI让大家可以获取园子首页最新随笔,只需验证一下Client是否有权限调用该API,不需要用户的授权。而如果Client需要进行发布博客的操作,就需要用户的授权,这时就要采用Authorization Code Grant

    DotNetOpenAuth是当前做得做好的基于.NET的OAuth开源实现,项目网址:https://github.com/DotNetOpenAuth

    Client Credentials Grant的流程图如下(图片1来源图片2来源):

    一、Client向Authorization Server请求access token

    主要操作如下:

    1. 由client_id和client_secret构建出credentials。

    2. 将credentials以http basic authentication的方式发送给Authorization Server。

    3. 从Authorization Server的响应中提取access token

    Client的实现代码如下:

    public async Task<ActionResult> SiteHome()
    {
        var client_id = "m.cnblogs.com";
        var client_secret = "20140213";
        var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(client_id + ":" + client_secret));
    
        var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
        var httpContent = new FormUrlEncodedContent(new
        Dictionary<string, string>
        {
            {"grant_type", "client_credentials"}
        });
    
        var response = await httpClient.PostAsync("https://authserver.open.cnblogs.com/oauth/token", httpContent);
    
        var responseContent = await response.Content.ReadAsStringAsync();
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            var accessToken = JObject.Parse(responseContent)["access_token"].ToString();
            return Content("AccessToken: " + accessToken);              
        }
        else
        {
            return Content(responseContent);
        }
    }

    二、Authorization Server验证Client,发放access token

    主要操作如下:

    1. Authorization Server通过IAuthorizationServerHost.GetClient()获取当前Client。

    2. Authorization Server通过IClientDescription.IsValidClientSecret()验证当前Client。

    3. 验证通过后,将access token包含在响应中发送给Client。

    主要实现代码如下(基于ASP.NET MVC):

    1. Authorization Server中Client实体类的实现代码(关键代码是IsValidClientSecret()的实现):

        public class Client : IClientDescription
        {
            public string Id { get; set; }
    
            public string Secret { get; set; }
    
            public Uri DefaultCallback
            {
                get { throw new NotImplementedException(); }
            }
    
            private ClientType _clientType;
            public ClientType ClientType
            {
                get { return _clientType; }
                set { _clientType = value; }
            }
    
            public bool HasNonEmptySecret
            {
                get { throw new NotImplementedException(); }
            }
    
            public bool IsCallbackAllowed(Uri callback)
            {
                throw new NotImplementedException();
            }
    
            public bool IsValidClientSecret(string secret)
            {
                return this.Secret == secret;
            }
        }

    AuthorizationServerHost的代码(关键代码是GetClient()与CreateAccessToken()的实现):

    public class AuthorizationServerHost : IAuthorizationServerHost
    {
        public static readonly ICryptoKeyStore HardCodedCryptoKeyStore = new HardCodedKeyCryptoKeyStore("...");
    
        public IClientDescription GetClient(string clientIdentifier)
        {
            return ServiceLocator.GetService<IClientService>().GetClient(clientIdentifier);
        }
    
        public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
        {
            var accessToken = new AuthorizationServerAccessToken
            {
                Lifetime = TimeSpan.FromHours(10),
                SymmetricKeyStore = this.CryptoKeyStore,
            };
            var result = new AccessTokenResult(accessToken);
            return result;
        }
    
        public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest)
        {
            //...
        }
    
        public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant
            (string userName, string password, IAccessTokenRequest accessRequest)
        {
            //...
        }        
    
        public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore CryptoKeyStore
        {
            get { return HardCodedCryptoKeyStore; }
        }
    
        public bool IsAuthorizationValid(IAuthorizationDescription authorization)
        {
            return true;
        }
    
        public INonceStore NonceStore
        {
            get { return null; }
        }
    }

    三、Client通过access token调用Resource Server上的API

    主要实现代码如下:

    public async Task<ActionResult> HomePosts(string blogApp)
    {
        //获取access token的代码见第1部分
        //...
        var accessToken = JObject.Parse(responseContent)["access_token"].ToString();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        response = await httpClient.GetAsync("https://api.open.cnblogs.com/blog/posts/sitehome");
        return Content(await response.Content.ReadAsStringAsync());               
    }

    四、Resource Server验证Client的access token,响应Client的API调用请求

    主要实现代码如下(基于ASP.NET Web API):

    1. 通过MessageHandler统一验证access token

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new BearerTokenHandler());
        }
    }

    2. BearerTokenHandler的实现代码(来自DotNetOpenAuth的示例代码):

    public class BearerTokenHandler : DelegatingHandler
    {
        protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == "Bearer")
            {
                var resourceServer = new DotNetOpenAuth.OAuth2.ResourceServer
                    (new StandardAccessTokenAnalyzer
                    (AuthorizationServerHost.HardCodedCryptoKeyStore));
    
                    var principal = await resourceServer.GetPrincipalAsync(request, cancellationToken);
                    HttpContext.Current.User = principal;
                    Thread.CurrentPrincipal = principal;
            }
    
            return await base.SendAsync(request, cancellationToken);
        }
    }

    3. Web API的示例实现代码:

    public class PostsController : ApiController
    {
        [Route("blog/posts/sitehome")]
        public async Task<IEnumerable<string>> GetSiteHome()
        {
            return new string[] { User.Identity.Name };
        }
    }

    四、Client得到Resouce Server的响应结果

    根据上面的Resouce Server中Web API的示例实现代码,得到的结果是:

    ["client:m.cnblogs.com"] 

    小结

    看起来比较简单,但实际摸索的过程是曲折的。分享出来,也许可以让初次使用DotNetOpenAuth的朋友少走一些弯路。

    【参考资料】

    The OAuth 2.0 Authorization Framework

    Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth

    Implementing an API Key with DotNetOpenAuth

  • 相关阅读:
    算法导论第三版第二章第三节习题答案
    算法导论第三版第二章第二节习题答案
    Android各版本 内外卡真实路径
    Go语言 爬虫2编码转换
    gotour的安装
    算法导论第三版第二章思考题答案
    Go语言的日志记录功能
    算法导论 中 lg 的底数是2的原因相关文章
    Go语言 爬虫1网络请求
    SharePoint 2013 母版页修改后,无法添加应用程序
  • 原文地址:https://www.cnblogs.com/dudu/p/oauth-dotnetopenauth-client-credentials-grant.html
Copyright © 2011-2022 走看看