1. 程式人生 > >Web Api Owin+Oauth2.0(ClientCredentials)+Jwt Token許可權認證控制

Web Api Owin+Oauth2.0(ClientCredentials)+Jwt Token許可權認證控制

OAuth簡介

OAuth簡單說就是一種授權的協議,只要授權方和被授權方遵守這個協議去寫程式碼提供服務,那雙方就是實現了OAuth模式。

OAuth 2.0 四種授權模式:

  • 授權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

下面的實列就是 客戶端模式(client credentials)

Jwt 簡介

JSON Web Token(JWT)是一個開放式標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間以JSON物件安全傳輸資訊。這些資訊可以通過數字簽名進行驗證和信任。可以使用祕密(使用HMAC演算法)或使用RSA的公鑰/私鑰對對JWT進行簽名。

實列講解

如上圖所示引入對應得Nuget包。

在專案中建立 Startup.cs 檔案,新增如下程式碼:

    /// <summary>
    ///     Startup
    /// </summary>
    public class Startup
    {
        private readonly HttpConfiguration _httpConfig;

        /// <summary>
        ///     Initializes a new instance of the <see cref="Startup" /> class.
        /// </summary>
        public Startup()
        {
            _httpConfig = new HttpConfiguration();
        }

        /// <summary>
        ///     Configurations the specified application.
        /// </summary>
        /// <param name="app">The application.</param>
        public void Configuration(IAppBuilder app)
        {
            ConfigureOAuthTokenGeneration(app);
            ConfigureOAuthTokenConsumption(app);
        }
        //配置token生成
        private void ConfigureOAuthTokenGeneration(IAppBuilder app)
        {
            var oAuthServerOptions = new OAuthAuthorizationServerOptions
            {
                //TODO:For Dev enviroment only (on production should be AllowInsecureHttp = false)
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/oauth/token"),//獲取token請求地址
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(5),//token過期時間
                Provider = new SimpleOAuthProvider(),//token生成服務
                AccessTokenFormat = new SimpleJwtFormat()//token生成Jwt格式
            };
            app.UseOAuthAuthorizationServer(oAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }
        //配置token使用
        private void ConfigureOAuthTokenConsumption(IAppBuilder app)
        {
            var issuer = ConfigurationManager.AppSettings["oauth:Issuer"];
            var audienceIds = ConfigurationManager.AppSettings["oauth:Audiences"];
            var audienceSecrets = ConfigurationManager.AppSettings["oauth:Secrets"];
            var allowedAudiences = audienceIds.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
            var base64Keys = audienceSecrets.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
            var keys = base64Keys.Select(s => TextEncodings.Base64Url.Decode(s)).ToList();
            app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = allowedAudiences,
                IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                {
                    new SymmetricKeyIssuerSecurityTokenProvider(issuer, keys)
                },
                Provider = new SimpleOAuthBearerAuthenticationProvider("access_token")
            });
        }

        
    }

SimpleOAuthProvider示例程式碼:

public class SimpleOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override  Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
           
            string clientId;
            string clientSecret;
            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
                context.TryGetFormCredentials(out clientId, out clientSecret);
            var authDbContext = new ExternalInterfaceBaseDbContext();
            ICustomerRepository customerRepository = new CustomerRepository(authDbContext);
            if (context.ClientId == null)
            {
                context.SetError("invalid_client", "The client_id is not set.");
                return Task.FromResult<object>(null); 
            }
            var secretKey = customerRepository.GetSecretKey(clientId);
            if (secretKey==null)
            {
                context.SetError("invalid_client", $"Invalid client_id '{context.ClientId}'.");
                return Task.FromResult<object>(null);
            }
            if (clientSecret != secretKey)
            {
                context.SetError("invalid_client", "Invalid client_Secret.");
                return Task.FromResult<object>(null);
            }

            context.Validated();
            return Task.FromResult<object>(null);
        }

        public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            context.OwinContext.Response.Headers["Access-Control-Allow-Origin"] = "*";
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "External Interface"));
            var properties = new Dictionary<string, string> { { "audience", context.ClientId ?? string.Empty } };
            var authenticationProperties = new AuthenticationProperties(properties);
            var ticket = new AuthenticationTicket(oAuthIdentity, authenticationProperties);
            context.Validated(ticket);
            return base.GrantClientCredentials(context);
        }
    }

使用Oauth2.0 ClientCredentials 模式獲取token,授予客戶憑證 。注意:不同得模式重寫。GrantResourceOwnerCredentials 內部可以呼叫外部服務,以進行對使用者賬戶資訊的驗證。

SimpleJwtFormat 示例程式碼:

public class SimpleJwtFormat : ISecureDataFormat<AuthenticationTicket>
    {
        private const string AudiencePropertyKey = "audience";
        private readonly string _issuer;
        private readonly string _audienceSecrets;

        public SimpleJwtFormat()
        {
            _issuer = ConfigurationManager.AppSettings["oauth:Issuer"];
            _audienceSecrets = ConfigurationManager.AppSettings["oauth:Secrets"];
        }

        public string Protect(AuthenticationTicket data)
        {
            if (data == null)
                throw new ArgumentNullException(nameof(data));
            var properties = data.Properties;
            var propertityDictionary = properties.Dictionary;
            var audienceId = propertityDictionary.ContainsKey(AudiencePropertyKey)
                ? propertityDictionary[AudiencePropertyKey]
                : null;
            if (string.IsNullOrWhiteSpace(audienceId))
                throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience.");
            if (properties.IssuedUtc == null)
                throw new InvalidOperationException("AuthenticationTicket.Properties does not include issued.");
            if (properties.ExpiresUtc == null)
                throw new InvalidOperationException("AuthenticationTicket.Properties does not include expires.");
            var issued = properties.IssuedUtc.Value.UtcDateTime;
            var expires = properties.ExpiresUtc.Value.UtcDateTime;
            //TODO:
            //var authDbContext = new InstrumentDbContext();
            //var audienceRepository = new AudienceRepository(authDbContext);
            //var audience = audienceRepository.Get(audienceId);
            var decodedSecret = TextEncodings.Base64Url.Decode(_audienceSecrets);
            var signingCredentials = new HmacSigningCredentials(decodedSecret);
            var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued, expires,
                signingCredentials);
            var handler = new JwtSecurityTokenHandler();
            var jwt = handler.WriteToken(token);
            return jwt;
        }
    }

根據授權Id生成Jwt Token返回。

SimpleOAuthBearerAuthenticationProvider 示例程式碼:

public class SimpleOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
    {
        private readonly string _accessTokenName;

        public SimpleOAuthBearerAuthenticationProvider(string accessTokenName)
        {
            _accessTokenName = accessTokenName;
        }

        public override Task RequestToken(OAuthRequestTokenContext context)
        {
            var token = context.Request.Query.Get(_accessTokenName);
            if (!string.IsNullOrEmpty(token))
                context.Token = token;
            return Task.FromResult<object>(null);
        }
    }

配置AccessToken使用。

到這裡許可權認證程式碼基本完成,最後就是在控制器或者方法上配置許可權控制特性 [Authorize]。

    [Authorize]
    [RoutePrefix("api/areas")]
    public class AreaController : ApiController

這樣Web Api Owin + Oauth2.0 + Jwt Token 的許可權認證框架就已經搭好了。