Client Credentials Grant是指直接由Client向Authorization Server請求access token,無需用戶(Resource Owner)的授權。比如我們提供OpenAPI讓大家可以獲取園子首頁最新隨筆,只需驗證一下Client是否有權限調用該API,不需要用戶的授權。而如果Client需要進行發布博客的操作,就需要用戶的授權,這時就要采用Authorization Code Grant。
DotNetOpenAuth是當前做得做好的基於.NET的OAuth開源實現,項目網址:https://github.com/DotNetOpenAuth。
Client Credentials Grant的流程圖如下(圖片1來源,圖片2來源):


一、Client向Authorization Server請求access token
主要操作如下:
1. 由client_id和client_secret構建出credentials。
2. 將credentials以http basic authentication的方式發送給Authorization Server。
3. 從Authorization Server的響應中提取access token
Client的實現代碼如下:
public async Task<ActionResult> SiteHome()
{
var client_id = "m.cnblogs.com";
var client_secret = "20140213";
var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(client_id + ":" + client_secret));
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
var httpContent = new FormUrlEncodedContent(new
Dictionary<string, string>
{
{"grant_type", "client_credentials"}
});
var response = await httpClient.PostAsync("https://authserver.open.cnblogs.com/oauth/token", httpContent);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var accessToken = JObject.Parse(responseContent)["access_token"].ToString();
return Content("AccessToken: " + accessToken);
}
else
{
return Content(responseContent);
}
}
二、Authorization Server驗證Client,發放access token
主要操作如下:
1. Authorization Server通過IAuthorizationServerHost.GetClient()獲取當前Client。
2. Authorization Server通過IClientDescription.IsValidClientSecret()驗證當前Client。
3. 驗證通過後,將access token包含在響應中發送給Client。
主要實現代碼如下(基於ASP.NET MVC):
1. Authorization Server中Client實體類的實現代碼(關鍵代碼是IsValidClientSecret()的實現):
public class Client : IClientDescription
{
public string Id { get; set; }
public string Secret { get; set; }
public Uri DefaultCallback
{
get { throw new NotImplementedException(); }
}
private ClientType _clientType;
public ClientType ClientType
{
get { return _clientType; }
set { _clientType = value; }
}
public bool HasNonEmptySecret
{
get { throw new NotImplementedException(); }
}
public bool IsCallbackAllowed(Uri callback)
{
throw new NotImplementedException();
}
public bool IsValidClientSecret(string secret)
{
return this.Secret == secret;
}
}
AuthorizationServerHost的代碼(關鍵代碼是GetClient()與CreateAccessToken()的實現):
public class AuthorizationServerHost : IAuthorizationServerHost
{
public static readonly ICryptoKeyStore HardCodedCryptoKeyStore = new HardCodedKeyCryptoKeyStore("...");
public IClientDescription GetClient(string clientIdentifier)
{
return ServiceLocator.GetService<IClientService>().GetClient(clientIdentifier);
}
public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
{
var accessToken = new AuthorizationServerAccessToken
{
Lifetime = TimeSpan.FromHours(10),
SymmetricKeyStore = this.CryptoKeyStore,
};
var result = new AccessTokenResult(accessToken);
return result;
}
public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest)
{
//...
}
public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant
(string userName, string password, IAccessTokenRequest accessRequest)
{
//...
}
public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore CryptoKeyStore
{
get { return HardCodedCryptoKeyStore; }
}
public bool IsAuthorizationValid(IAuthorizationDescription authorization)
{
return true;
}
public INonceStore NonceStore
{
get { return null; }
}
}
查看本欄目
三、Client通過access token調用Resource Server上的API
主要實現代碼如下:
public async Task<ActionResult> HomePosts(string blogApp)
{
//獲取access token的代碼見第1部分
//...
var accessToken = JObject.Parse(responseContent)["access_token"].ToString();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
response = await httpClient.GetAsync("https://api.open.cnblogs.com/blog/posts/sitehome");
return Content(await response.Content.ReadAsStringAsync());
}
四、Resource Server驗證Client的access token,響應Client的API調用請求
主要實現代碼如下(基於ASP.NET Web API):
1. 通過MessageHandler統一驗證access token
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new BearerTokenHandler());
}
}
2. BearerTokenHandler的實現代碼(來自DotNetOpenAuth的示例代碼):
public class BearerTokenHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Headers.Authorization != null && request.Headers.Authorization.Scheme == "Bearer")
{
var resourceServer = new DotNetOpenAuth.OAuth2.ResourceServer
(new StandardAccessTokenAnalyzer
(AuthorizationServerHost.HardCodedCryptoKeyStore));
var principal = await resourceServer.GetPrincipalAsync(request, cancellationToken);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
return await base.SendAsync(request, cancellationToken);
}
}
3. Web API的示例實現代碼:
public class PostsController : ApiController
{
[Route("blog/posts/sitehome")]
public async Task<IEnumerable<string>> GetSiteHome()
{
return new string[] { User.Identity.Name };
}
}
四、Client得到Resouce Server的響應結果
根據上面的Resouce Server中Web API的示例實現代碼,得到的結果是:
["client:m.cnblogs.com"]
小結
看起來比較簡單,但實際摸索的過程是曲折的。分享出來,也許可以讓初次使用DotNetOpenAuth的朋友少走一些彎路。