在前一篇博文中,咱們經過以 OAuth 的 Client Credential Grant 受權方式(只驗證調用客戶端,不驗證登陸用戶)拿到的 Access Token ,成功調用了與用戶無關的 Web API。html
在這篇博文中,咱們將以 OAuth 的 Resource Owner Password Credentials Grant 的受權方式( grant_type=password )獲取 Access Token,並以這個 Token 調用與用戶相關的 Web API。api
對應的應用場景是:爲自家的網站開發手機 App(非第三方 App),只需用戶在 App 上登陸,無需用戶對 App 所能訪問的數據進行受權。安全
根據 OAuth 規範,客戶端獲取 Access Token 的請求方式以下:app
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=password&username=johndoe&password=A3ddj3w
根據上面的請求方式,在 C# 中用 HttpClient 實現一個簡單的客戶端,代碼以下:async
public class OAuthClientTest { private HttpClient _httpClient; public OAuthClientTest() { _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com"); } [Fact] public async Task Get_Accesss_Token_By_Resource_Owner_Password_Credentials_Grant() { Console.WriteLine(await GetAccessToken()); } private async Task<string> GetAccessToken() { var clientId = "1234"; var clientSecret = "5678"; var parameters = new Dictionary<string, string>(); parameters.Add("grant_type", "password"); parameters.Add("username", "博客園團隊"); parameters.Add("password", "cnblogs.com"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)) ); var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)); var responseValue = await response.Content.ReadAsStringAsync(); if (response.StatusCode == System.Net.HttpStatusCode.OK) { return JObject.Parse(responseValue)["access_token"].Value<string>(); } else { Console.WriteLine(responseValue); return string.Empty; } } }
(注:與以前相比,這裏的 client_id/client_secret 改成了 Basic Authorization,以更好的遵循 OAuth 規範)ide
在服務端,基於 Owin OAuth, 針對 Resource Owner Password Credentials Grant 的受權方式,只需重載 OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials() 方法便可。代碼以下:測試
public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider { //... public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { //調用後臺的登陸服務驗證用戶名與密碼 var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties()); context.Validated(ticket); await base.GrantResourceOwnerCredentials(context); } }
完整的CNBlogsAuthorizationServerProvider實現代碼以下(與以前相比,context.TryGetFormCredentials 改成了 context.TryGetBasicCredentials):網站
public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider { public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId; string clientSecret; context.TryGetBasicCredentials(out clientId, out clientSecret); if (clientId == "1234" && clientSecret == "5678") { context.Validated(clientId); } await base.ValidateClientAuthentication(context); } public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties()); context.Validated(ticket); await base.GrantClientCredentials(context); } public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { //調用後臺的登陸服務驗證用戶名與密碼 var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties()); context.Validated(ticket); await base.GrantResourceOwnerCredentials(context); } }
這樣,運行客戶端程序就能夠拿到 Access Token 了。url
接下來,咱們拿着以這種方式獲取的 Access Token,就能夠調用與用戶相關的 Web API 了。spa
在服務端咱們經過一個簡單的 Web API 測試一下,代碼以下:
public class UsersController : ApiController { [Authorize] public string GetCurrent() { return User.Identity.Name; //這裏能夠調用後臺用戶服務,獲取用戶相關數所,或者驗證用戶權限進行相應的操做 } }
而後,客戶端用以 grant_type=password 方式拿到的 Access Token 調用這個Web API,客戶端增長的代碼以下:
[Fact] public async Task Call_WebAPI_By_Resource_Owner_Password_Credentials_Grant() { var token = await GetAccessToken(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); Console.WriteLine(await (await _httpClient.GetAsync("/api/users/current")).Content.ReadAsStringAsync()); }
客戶端運行結果以下:
"博客園團隊"
調用成功!運行結果正是獲取 Access Token 時所用的 username 。
結合 ASP.NET 現有的安全機制,藉助 OWIN 的威力,Microsoft.Owin.Security.OAuth 的確讓開發基於 OAuth 的 Web API 變得更簡單。