实现Client Credentials Grant

[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 Credentials Grant

 

实现Client Credentials Grant

一、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

 
 
 
标签:  OAuthDotNetOpenAuth

你可能感兴趣的:(OAuth,DotNetOpenAuth)