[OAuth]基于DotNetOpenAuth实现Client Credentials Grant

摘要:
如果客户端需要发布博客,则需要用户的授权。在这种情况下,应使用AuthorizationCodeGrant。DotNetOpenAuth是基于.NET的OAuth的开源实现。项目网站为:https://github.com/DotNetOpenAuth 。 ClientCredentialsGrant的流程图如下:1.客户端向AuthorizationServer请求accesstoken。主要操作如下:1.client_ID和client_Secret构建凭据。

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来源):

[OAuth]基于DotNetOpenAuth实现Client Credentials Grant第1张

[OAuth]基于DotNetOpenAuth实现Client Credentials Grant第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

免责声明:文章转载自《[OAuth]基于DotNetOpenAuth实现Client Credentials Grant》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Shapefile 文件扩展名Centos7下配置Python3和Python2共存,以及对应版本Ipython安装配置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

.NET插件系统

面临的问题       在开发插件系统中,我们通常会面临这样的问题:        一些功能并不是在开启时就要被使用的,例如VS中的大量功能对一个大部分程序员来说用不着,但框架本身却应该向用户提供该插件的相应信息?        在可视化的插件功能列表中,我们不仅希望提供简单的插件名称信息,更希望能以图片,或动画等形式展示其功能特性,便于用户选择。   ...

【python 3.6】从网站抓图并存放到本地路径

#!/usr/bin/python # -*- coding: UTF-8 -*- _author_ = 'BH8ANK' import urllib.request import re import os import time #os.rmdir("D:/images") #1,打开页面,读取图片张数,抓html wangzhi = "https...

c#dev操作读取excel方法

一:使用spreadsheetControl1 方法 1:打开excel; private void barButtonItem1_ItemClick(objectsender, DevExpress.XtraBars.ItemClickEventArgs e) { //if (Convert.ToInt32...

使用curses管理基于文本的屏幕--(八)

CD管理程序现在我们已经了解了curses所提供了功能,我们可以继续开发我们的例子程序。在这里所展示是一个使用curses库的C语言版本。他提供了一些高级的特性,包括更为清晰的屏幕信息显示以及用于跟踪列表的滚动窗口。完整的程序共页长,所以我们将其分为几部分,在每一部分中介绍一些函数。试验--一个新的CD管理程序1 首先,我们包含所有的头文件以及一些全局常量...

C++(四十八) — string容器的基本操作

参考博客:https://blog.csdn.net/qq_37941471/article/details/82107077 https://www.cnblogs.com/danielStudy/p/7127564.html#top 1、声明一个字符串 标准库类型string表示可变长的字符序列,为了在程序中使用string类型,我们必须包含头文件:#...

Redis——Springboot集成Redis集群

前言 在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,springboot 2.x版本中默认客户端是用 lettuce实现的。 Lettuce 与 Jedis 比较 Lettuce 和 Jedis 的都是连接 Redis Server的客户端。 Jedis 在实现上是直连 redis server,多线程环境下非线...