Oauth 2.0 协议及应用

Oauth 2.0 协议及应用

Posted by YangLong on May 7, 2017

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
  1. 由client_id和client_secret构建出credentials。
  2. 将credentials以http basic authentication的方式发送给Authorization Server。
  3. 从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

  1. Authorization Server通过IAuthorizationServerHost.GetClient()获取当前Client。
  2. Authorization Server通过IClientDescription.IsValidClientSecret()验证当前Client。
  3. 验证通过后,将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);
    }

}