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
{ 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
} 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
{ //获取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
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的示例实现代码,得到的结果是:
小结
看起来比较简单,但实际摸索的过程是曲折的。分享出来,也许可以让初次使用DotNetOpenAuth的朋友少走一些弯路。
【参考资料】
The OAuth 2.0 Authorization Framework
Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth