Why use Oauth 2.0?
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC .
OAuth2.0的用户授权过程有3步:
- 用户到授权服务器,请求授权,然后返回授权码(AuthorizationCode)
- 客户端由授权码到授权服务器换取访问令牌(access token)
- 用访问令牌去访问得到授权的资源、
总结:获取授权码(Authorization Code)—>换取访问令牌(access_token)—>访问资源
OAuth2.0 四种角色:
- Resource Owner 用户,又叫资源所有者 -User
- Client 客户端,俗称第三方应用
- Authorzation Server 授权服务端,颁发AccessToken
- Resource Server 资源服务端,根据AccessToken开放相应的资源访问权限
OAuth2.0 四种授权模式:
- Authorization Code 授权码(认证码)模式 response_type=code 这是现在互联网应用中最常见的授权模式。客户端引导用户在授权服务端输入凭证获取用户授权(AccessToken),进而访问用户资源。需要注意的是,在用户授权后,授权服务端先回传客户端授权码,然后客户端再使用授权码换取AccessToken。为什么不直接返回AccessToken呢?主要是由于用户授权后,授权服务端重定向到客户端地址(必须的,用户可不愿停留在授权服务端或者重新敲地址),此时数据只能通过QueryString方式向客户端传递,在用户浏览器地址栏中可见,不安全,于是分成了两步。第二步由客户端主动请求获取最终的令牌。
- Implict 简化(隐形)模式 response_type=token
- Resource Owner Password Credential 用户名密码模式 grant_type=password 客户端乃是授权服务端的信任合作方,不需要用户参与授权,事先就约定向其开放指定资源(不特定于用户)的访问权限。客户端通过证书或密钥(或其它约定形式)证明自己的身份,获取AccessToken,用于后续访问
- Client Credential客户端模式 grant_type=client_credential、 客户端被用户和授权服务端高度信任,用户直接在客户端中输入用户名密码,然后客户端传递用户名密码至授权服务端获取AccessToken,便可访问相应的用户资源。这在内部多系统资源共享、同源系统资源共享等场景下常用,比如单点登录,在登录时就获取了其它系统的AccessToken,避免后续授权,提高了用户体验。
三类凭证:
- AuthorizationCode:授权码,授权服务端和客户端之间传输。
- AccessToken:访问令牌,授权服务端发给客户端,客户端用它去到资源服务端请求资源。
- RefreshToken:刷新令牌,授权服务端和客户端之间传输。
How use OAuth2.0?
客户端
Client 请求Access token
- 由client_id和client_secret构建出credentials。
- 将credentials以http basic authentication的方式发送给Authorization Server。
- 从Authorization Server的响应中提取access token
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); } }
客户端使用 Access Token 去 Resource Server请求资源
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()); }
客户端获得请求的资源结果
授权服务器端:Authozation Server 验证Client ,授予 Access Token
- Authorization Server通过IAuthorizationServerHost.GetClient()获取当前Client。
- Authorization Server通过IClientDescription.IsValidClientSecret()验证当前Client。
-
验证通过后,将access token包含在响应中发送给Client。 ```cs 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; } }
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; }
} } ```
资源服务器端:Resource Service 验证 Access Token ,相应Web API访问
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new BearerTokenHandler());
}
}
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);
}
}