如何基於Microsoft.Owin.Security.OAuth,使用Client Credentials
Grant受權方式給客戶端發放access token? Client Credentials
Grant的受權方式就是隻驗證客戶端(Client),不驗證用戶(Resource Owner),只要客戶端經過驗證就發access
token。舉一個對應的應用場景例子,好比咱們想提供一個「獲取網站首頁最新博文列表」的WebAPI給客戶端App調用。因爲這個數據與用戶無關,因此不涉及用戶登陸與受權,不須要Resource
Owner的參與。但咱們不想任何人均可以調用這個WebAPI,因此要對客戶端進行驗證,而使用OAuth中的 Client
Credentials Grant 受權方式能夠很好地解決這個問題。html
在App_Start文件夾下新增ApplicationDbInitializer,代碼以下:ajax
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> { protected override void Seed(ApplicationDbContext context) { InitializeIdentityForEF(context); base.Seed(context); } //建立用戶名爲admin@123.com,密碼爲「Admin@123456」 public static void InitializeIdentityForEF(ApplicationDbContext dbContext) { var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); const string name = "admin@123.com";//用戶名 const string email = "admin@123.com";//郵箱 const string password = "Admin@123456";//密碼 //若是沒有admin@123.com用戶則建立該用戶 var user = userManager.FindByName(name); if (user == null) { user = new ApplicationUser { UserName = name, Email = email }; var result = userManager.Create(user, password); result = userManager.SetLockoutEnabled(user.Id, false); } } }
修改Model文件夾下的IdentityModels.cs,添加斜體部分代碼,需添加命名空間:using System.Data.Entity;數據庫
public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { // 在第一次啓動網站時初始化數據庫添加管理員用戶憑據到數據庫 Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer()); }
我把WebApi的Controller放到一個新建的文件夾APIControllers中,TestController的View的js的測試代碼json
打開Startup.Auth.cs,如下代碼是Oauth相關的配置代碼api
public partial class Startup { public void ConfigureAuth(IAppBuilder app) { var OAuthOptions = new OAuthAuthorizationServerOptions { //獲取Token的路徑 TokenEndpointPath = new PathString("/Token"), Provider = new ApplicationOAuthProvider(PublicClientId), AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), //Token 過時時間,默認20分鐘 AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), //在生產模式下設 AllowInsecureHttp = false AllowInsecureHttp = true }; app.UseOAuthBearerTokens(OAuthOptions); } }
使用Client Credentials Grant的受權方式( grant_type= client_credentials)獲取 Access Token,並以這個 Token 調用與用戶相關的 Web API。cookie
咱們須要修改部分代碼,修改ValidateClientAuthentication()方法,繼承實現GrantClientCredentials()方法。代碼以下app
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId, clientSecret; context.TryGetBasicCredentials(out clientId, out clientSecret); if (clientId == "Mobile" && clientSecret == "Xiaomi") { context.Validated(); } return Task.FromResult<object>(null); } public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "Xiaomi")); var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties()); context.Validated(ticket); return base.GrantClientCredentials(context); } }
在 ValidateClientAuthentication() 方法中獲取客戶端的 client_id 與 client_secret 進行驗證。在 GrantClientCredentials() 方法中對客戶端進行受權,授了權就能發 access token 。這樣,OAuth的ClientCredentials受權服務端代碼就完成了。在ASP.NET Web API中啓用OAuth的Access Token驗證很是簡單,只需在相應的Controller或Action加上[Authorize]標記,VS已生成部分代碼,詳細查看APIController文件夾下的ValuesControllerasync
下面咱們在客戶端調用一下,添加TestController,生成Index的View,而後在View中添加以下ide
$(function () { $("#clientCredentials").on("click", function () { GetClientCredentialsAccessToken(); }); }); function GetClientCredentialsAccessToken() { $("#clientResult").html("Requesting"); var clientId = "Mobile"; var clientSecret = "Xiaomi"; $.ajax({ url: "/Token", type: "post", data: { "grant_type": "client_credentials" }, dataType: "json", headers: { "Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret) }, success: function (data) { var accessToken = data.access_token; GetValues(accessToken); } }); } function GetValues(accessToken) { var html = "Token:" + accessToken + "<br/><br/>"; $.ajax({ url: "/api/Values", type: "get", dataType: "json", headers: { "Authorization": "Bearer " + accessToken }, success: function (values) { for (var i = 0; i < values.length; i++) { html += "values[" + i + "] :" + values[i] + "<br/>"; } $("#clientResult").html(html); } }); } function Base64_Encode(str) { var c1, c2, c3; var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var i = 0, len = str.length, string = ''; while (i < len) { c1 = str.charCodeAt(i++) & 0xff; if (i === len) { string += base64EncodeChars.charAt(c1 >> 2); string += base64EncodeChars.charAt((c1 & 0x3) << 4); string += "=="; break; } c2 = str.charCodeAt(i++); if (i === len) { string += base64EncodeChars.charAt(c1 >> 2); string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); string += base64EncodeChars.charAt((c2 & 0xF) << 2); string += "="; break; } c3 = str.charCodeAt(i++); string += base64EncodeChars.charAt(c1 >> 2); string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); string += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); string += base64EncodeChars.charAt(c3 & 0x3F); } return string; }
測試結果:post
Token:4iIu7HProfJaxRiklsl-ORRO3hdyrsu50pQc1Eh2-Q5lSWK8UJgz6719ZaeeULhwkMPpEFYfk6QDOOMEyFqULULk65Sb0JY29wskyZyQhKJ3_P-eSVQ2PlbKbjH9ZcziAZsVOiNLp8CfUqL5qWUq8ggVAa8KRcnlJ1DIVWnEu0XvTEDZaLDpFqqj2Cex2CX7TmTgfs07RUBdx5_3WDavNA
Ps:
傳遞clientId與clientSecret有兩種方式,上例使用BasicAuthentication,服務端使用TryGetBasicCredentials();另一種方式是普通From的,把參數放到Ajax的data中,如:
{「clientId」: id ,」 clientSecret」:」secret」, "grant_type":"client_credentials"}
對應服務端使用TryGetFormCredentials()獲取clientId和clientSecret;
推薦使用Basic Authentication方式;
使用Resource Owner Password Credentials Grant 的受權方式( grant_type=password )獲取 Access Token,並以這個 Token 調用與用戶相關的 Web API。
Resource Owner Password Credentials Grant 受權方式(須要驗證登陸用戶)
由於咱們剛開始時已經初始化EF,添加了一個用戶信息。ApplicationOAuthProvider.cs 的GrantResourceOwnerCredentials()方法(VS幫咱們自動生成了),已經實現了先關的代碼
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { //調用後臺的登陸服務驗證用戶名與密碼 var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>(); ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password); if (user == null) { context.SetError("invalid_grant", "用戶名或密碼不正確。"); return; } ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType); ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType); AuthenticationProperties properties = CreateProperties(user.UserName); AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties); context.Validated(ticket); context.Request.Context.Authentication.SignIn(cookiesIdentity); }
添加一個測試用的Controller
public class UsersController : ApiController { [Authorize] public string GetCurrent() { return User.Identity.Name; //這裏能夠調用後臺用戶服務,獲取用戶相關數所,或者驗證用戶權限進行相應的操做 } }
在Test的index.cshtml 中新增測試的代碼,以下
function GetResourceOwnerCredentialsAccessToken() { $("#resourceOwnerresult").html("Requesting"); var clientId = "Mobile"; var clientSecret = "Xiaomi"; $.ajax({ url: "/Token", type: "post", data: { "grant_type": "password", "username": "admin@123.com", "password": "Admin@123456" }, dataType: "json", headers: { "Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret) }, success: function (data) { var accessToken = data.access_token; GetCurrentUserName(accessToken); } }); } function GetCurrentUserName(accessToken) { var html = "Token:" + accessToken + "<br/><br/>"; $.ajax({ url: "/api/User", type: "get", dataType: "text", headers: { "Authorization": "Bearer " + accessToken }, success: function (userName) { html += "CurrentUserName:" + userName + "<br/>"; $("#resourceOwnerresult").html(html); } }); }
測試結果以下
Token:Cvct6BAKix_xLNEEOfidpEG0ymJihOSjdACazP2R2tJSB3TKVnxicgQK27DzDrICUC4A7vITqhkhBRsT5cRgiow--VkbiR4we3yQ54tc6B_W8KRrdGabjase_gpmFv8oYUPGLpI82acDpcZPzCkmgLLwAq8qfkmlK7iHm5tLM6-NRR8tgfEeOVBljHq4smIXw_eVuces3sRQm-PXTD4xmp05JdrJ9zFeRb_SAN0ADqDJfJxk1nNooCtdJyeHB6r1S2D81H6P7bhRK_edneWdkX5QCNBHL8b39UKnnk0ywza6vXcWct4RaATBYOw20iNu0XR6JRx5opP9vqqC2ag8Ux6s3GHl-vAZTaYuwunmWyY0FyJJWpjNnFpPo-pkxZaK1XJxgGPpSV-JJjEZLarnq9O57hQGfbVLCd3KtWuJflo5rMnfkAz2nXlcd3gAgjIhipAIlpsG72StzN0qBL8Ml2XvV9Re1Z8U4QtrE7tzjkE CurrentUserName:"admin@123.com"
至此,使用WebApi 的兩種受權方式發放Token和兩種受權方式區別,以及使用Token調用受保護的api已經介紹完了,Oauth其實還有一個refresh token,refresh token 是專用於刷新 access token 的 token。一是由於 access token 是有過時時間的,到了過時時間這個 access token 就失效,須要刷新;二是由於一個 access token 會關聯必定的用戶權限,若是用戶受權更改了,這個 access token 須要被刷新以關聯新的權限。
這個就不單獨介紹了,有興趣的能夠本身研究下。
Asp.Net 高級技術羣 89336052,羣共享有源碼
感謝:晨風的指導和教育,提供demo和文章,我只是一個發佈文章的戰五渣!