在ASP.NET中基於Owin OAuth使用Client Credentials Grant受權發放Token

Client Credentials Grant的受權方式就是隻驗證客戶端(Client),不驗證用戶(Resource Owner),只要客戶端經過驗證就發access token。html

舉一個對應的應用場景例子,好比咱們想提供一個「獲取網站首頁最新博文列表」的WebAPI給iOS App調用。api

因爲這個數據與用戶無關,因此不涉及用戶登陸與受權,不須要Resource Owner的參與。mvc

但咱們不想任何人均可以調用這個WebAPI,因此要對客戶端進行驗證,而使用OAuth中的Client Credentials Grant受權方式能夠很好地解決這個問題。app

 

1)用Visual Studio 2013/2015建立一個Web API 4項目,VS會生成一堆OAuth相關代碼。async

2)打開App_Start/Startup.Auth.cs ,精簡一下代碼,咱們只須要實現以Client Credentials Grant受權方式拿到token,其它無關代碼所有清除,最終剩下以下代碼:ide

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 進行驗證。ui

在 GrantClientCredentials() 方法中對客戶端進行受權,授了權就能發 access token 。spa

這樣,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

相關文章
相關標籤/搜索