• 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token


    Client Credentials Grant的授权方式就是只验证客户端(Client),不验证用户(Resource Owner),只要客户端通过验证就发access token。

    举一个对应的应用场景例子,比如我们想提供一个“获取网站首页最新博文列表”的WebAPI给iOS App调用。

    由于这个数据与用户无关,所以不涉及用户登录与授权,不需要Resource Owner的参与。

    但我们不想任何人都可以调用这个WebAPI,所以要对客户端进行验证,而使用OAuth中的Client Credentials Grant授权方式可以很好地解决这个问题。

    1)用Visual Studio 2013/2015创建一个Web API 4项目,VS会生成一堆OAuth相关代码。

    2)打开App_Start/Startup.Auth.cs ,精简一下代码,我们只需要实现以Client Credentials Grant授权方式拿到token,其它无关代码全部清除,最终剩下如下代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using Microsoft.Owin;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.Google;
    using Microsoft.Owin.Security.OAuth;
    using Owin;
    using WebApi4.Providers;
    using WebApi4.Models;
    
    namespace WebApi4
    {
        public partial class Startup
        {
            public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    
            public static string PublicClientId { get; private set; }
    
            // 有关配置身份验证的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=301864
            public void ConfigureAuth(IAppBuilder app)
            {
                var OAuthOptions = new OAuthAuthorizationServerOptions
                {
                    TokenEndpointPath = new PathString("/token"),//获取Token的地址 示例:http://localhost:54342/token
                    Provider = new AuthorizationServerProvider(),//
                    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),//Token有效期
                    AllowInsecureHttp = true,
             RefreshTokenProvider = new RefreshTokenProvider()//应用RefreshTokenProvider,刷新Token的程序 }; app.UseOAuthBearerTokens(OAuthOptions); } } }

    刷新Token的程序

    using Microsoft.Owin.Security.Infrastructure;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Threading.Tasks;
    using System.Web;
    using WebApi4.Interfaces;
    using WebApi4.Models;
    
    namespace WebApi4.Providers
    {
        public class RefreshTokenProvider : AuthenticationTokenProvider
        {
            private static ConcurrentDictionary<string, string> _refreshTokens = new ConcurrentDictionary<string, string>();
    
            public override void Create(AuthenticationTokenCreateContext context)
            {
                string tokenValue = Guid.NewGuid().ToString("n");
    
                context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
                context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(60);
    
                _refreshTokens[tokenValue] = context.SerializeTicket();
    
                context.SetToken(tokenValue);
            }
    
            public override void Receive(AuthenticationTokenReceiveContext context)
            {
                string value;
                if (_refreshTokens.TryRemove(context.Token, out value))
                {
                    context.DeserializeTicket(value);
                }
            }
        }
    }
    

      

    3)创建一个新的类 AuthorizationServerProvider,并继承自 OAuthAuthorizationServerProvider,重载 OAuthAuthorizationServerProvider() 与 GrantClientCredentials() 这两个方法。代码如下:

    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.OAuth;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using System.Web;
    
    namespace WebApi4.Providers
    {
        public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
        {
            public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {
                string clientId;
                string clientSecret;
    
                //省略了return之前context.SetError的代码
                if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { return; }
                //保存client_id
                context.OwinContext.Set<string>("client_id", clientId);
    
                //context.OwinContext.Set<string>("clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
    
                context.Validated(clientId);
    
                await base.ValidateClientAuthentication(context);
            }
    
            public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
            {
                var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
    
                var props = new AuthenticationProperties(new Dictionary<string, string> { { "client_id", context.ClientId } });
    
                oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.ClientId));
    
                var ticket = new AuthenticationTicket(oAuthIdentity, props);
    
                //var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
    
                context.Validated(ticket);
    
                await base.GrantClientCredentials(context);
            }
    
            public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
            {
                //验证context.UserName与context.Password //调用后台的登录服务验证用户名与密码
                var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
    
                var props = new AuthenticationProperties(new Dictionary<string, string> { { "client_id", context.ClientId } });
    
                oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
    
                var ticket = new AuthenticationTicket(oAuthIdentity, props);
    
                context.Validated(ticket);
    
                await base.GrantResourceOwnerCredentials(context);
            }
    
            public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
            {
                var originalClient = context.Ticket.Properties.Dictionary["client_id"];
    
                var currentClient = context.ClientId;
    
                if (originalClient != currentClient)
                {
                    context.Rejected();
                    return;
                }
    
                var oAuthIdentity = new ClaimsIdentity(context.Ticket.Identity);
    
                var props = new AuthenticationProperties(new Dictionary<string, string> { { "client_id", context.ClientId } });
    
                oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.ClientId));//"newClaim", "refreshToken"
    
                var newTicket = new AuthenticationTicket(oAuthIdentity, context.Ticket.Properties);
    
                context.Validated(newTicket);
    
                await base.GrantRefreshToken(context);
            }
        }
    }
    

    在 ValidateClientAuthentication() 方法中获取客户端的 client_id 与 client_secret 进行验证。

    在 GrantClientCredentials() 方法中对客户端进行授权,授了权就能发 access token 。

    这样,OAuth的服务端代码就完成了。

    4)然后写客户端调用代码测试一下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Script;
    using System.Web.Script.Serialization;
    
    namespace WebApi4.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                ViewBag.Title = "Home Page";
                return View();
            }
    
            /// <summary>
            /// 使用 client_credentials 方式获得Token
            /// </summary>
            /// <returns></returns>
            public ContentResult Get_Accesss_Token_By_Client_Credentials_Grant()
            {
                var clientId = "xsj";//用户名
                var clientSecret = "1989";//密码
    
                HttpClient _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:54342");
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));
    
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "client_credentials");
    
                string result = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
                return Content(result);
            }
    
            /// <summary>
            /// 使用 password 方式获得Token
            /// </summary>
            /// <returns></returns>
            public ContentResult Get_Accesss_Token_By_Password_Grant()
            {
                var clientId = "xsj";//用户名
                var clientSecret = "1989";//密码
    
                HttpClient _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:54342");
    
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "password");
                parameters.Add("username", clientId);
                parameters.Add("password", clientSecret);
    
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));
                var response = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
                string responseValue = response.Result.Content.ReadAsStringAsync().Result;
    
                return Content(responseValue);
            }
    
            /// <summary>
            /// 根据上一次获取的 refresh_token 来获取新 Token
            /// </summary>
            /// <param name="refresh_token"></param>
            /// <returns></returns>
            public ContentResult Get_Access_Token_By_RefreshToken(string refresh_token)
            {
                var clientId = "xsj";
                var clientSecret = "1989";
    
                HttpClient _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:54342");
    
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "refresh_token");
                parameters.Add("refresh_token", refresh_token);
    
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                    "Basic",
                    Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));
    
                var response = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
                string responseValue = response.Result.Content.ReadAsStringAsync().Result;
    
                return Content(responseValue);
            }
    
            /// <summary>
            /// 测试用 访问一个受限的API接口
            /// </summary>
            /// <returns></returns>
            public ContentResult TokenTest()
            {
                HttpClient _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:54342");
    
                string token = GetAccessToken();
                TokenInfo tinfo = new JavaScriptSerializer().Deserialize<TokenInfo>(token);
    
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tinfo.access_token);
                return Content(_httpClient.GetAsync("/api/Account/Test").Result.Content.ReadAsStringAsync().Result);
            }
    
            /// <summary>
            /// 测试用 获得一个Token
            /// </summary>
            /// <returns></returns>
            public string GetAccessToken()
            {
                var clientId = "xsj";//用户名
                var clientSecret = "1989";//密码
                HttpClient _httpClient = new HttpClient();
                _httpClient.BaseAddress = new Uri("http://localhost:54342");
    
                var parameters = new Dictionary<string, string>();
                parameters.Add("grant_type", "password");
                parameters.Add("username", clientId);
                parameters.Add("password", clientSecret);
    
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));
                var response = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
                string responseValue = response.Result.Content.ReadAsStringAsync().Result;
                return responseValue;
            }
        }
    
        public class TokenInfo
        {
            public string access_token { get; set; }
            public string token_type { get; set; }
            public long expires_in { get; set; }
            public string refresh_token { get; set; }
        }
    }
    

    返回结果:

    {"access_token":"W2m0pUxHLWpb2p6Ys25g....","token_type":"bearer","expires_in":1209599,"refresh_token":"4b45asdfa5fe1a5e548c0f"}
    

    注:使用Basic Authentication传递clientId与clientSecret,服务端AuthorizationServerProvider中的TryGetFormCredentials()改为TryGetBasicCredentials()

    使用Fiddler获得Token:

    使用得到的Token访问受限的接口,需要在Header中加入Token:Authorization: bearer {Token}

    Authorization: bearer 9R5KsWyFmOYEbQs9qNCgnpZqDpkLkvjW5aVN6j5c6kDegDg...

    受限的Action

    在ASP.NET Web API中启用OAuth的Access Token验证非常简单,只需在相应的Controller或Action加上[Authorize]标记,比如:

    [AcceptVerbs("GET")]
    [Authorize]
    public HttpResponseMessage GetUserInfo(int ID){......}

    加上[Authorize]之后,如果不使用Access Token,调用API时就会出现如下的错误:{"Message":"已拒绝为此请求授权。"}

    这时你也许会问,为什么一加上[Authorize]就会有这个效果?原来的Forms验证怎么不起作用了?

    原因是你在用Visual Studio创建ASP.NET Web API项目时,VS自动帮你添加了相应的代码,打开WebApiConfig.cs,你会看到下面这2行代码:

    // Web API 配置和服务
    // 将 Web API 配置为仅使用不记名令牌身份验证。
    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    

      

    【参考资料】

    http://www.cnblogs.com/dudu/p/4569857.html

    http://www.hackered.co.uk/articles/asp-net-mvc-creating-an-oauth-client-credentials-grant-type-token-endpoint

    http://www.cnblogs.com/YamatAmain/p/5029466.html

    http://www.cnblogs.com/xizz/archive/2015/12/18/5056195.html

  • 相关阅读:
    tomcat启动与关闭脚本
    SqlAlchemy ORM
    python之socket
    python异常处理
    python常用模块
    PYTHON之文件操作
    Linux系统Load average负载详细解释
    tomcat报错:This is very likely to create a memory leak问题解决
    springcloud第一步:创建eureka注册服务
    SpringCloud微服务高级
  • 原文地址:https://www.cnblogs.com/xsj1989/p/5557251.html
Copyright © 2020-2023  润新知