如何基于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和文章,我只是一个发布文章的战五渣!