基於OWIN ASP.NET WebAPI 使用OAUTH2受權服務的幾點優化

前面在ASP.NET WEBAPI中集成了Client Credentials GrantResource Owner Password Credentials Grant兩種OAUTH2模式,今天在調試Client Credentials Grant想到以下幾點html

  • 建議TryGetBasicCredentials認證 validate client credentials should be stored securely (salted, hashed, iterated),參考PDF設計
  • 增長token額外字段
  • 增長scope受權字段
  • 持久化Token
  • 刷新Token後失效老的Token
  • 自定義驗證【重啓IIS池Token失效,驗證權限】

優化點

1.啓用TryGetBasicCredentials認證:Basic Authentication傳遞clientId與clientSecret,服務端中的TryGetFormCredentials()改成TryGetBasicCredentials()
2.增長token額外字段:須要重寫TokenEndpoint方法
http://stackoverflow.com/questions/26357054/return-more-info-to-the-client-using-oauth-bearer-tokens-generation-and-owin-in ,通常無特殊要求,不建議加
3.參數中傳soap字段,以空格分隔的權限列表,若不傳遞此參數,表明請求用戶的默認權限
4.重寫AccessTokenProvider中CreateAsync方法,生成Token值持久化相關信息到DB
5.重寫AccessTokenProvider中ReceiveAsync方法,驗證Token是否有效
api

服務實現

配置Startup app

/// <summary>
        /// IOS App OAuth2 Credential Grant Password Service /// </summary>
        /// <param name="app"></param>
        public void ConfigureAuth(IAppBuilder app) { //ClientApplicationOAuthProvider
            app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions { //AuthorizeEndpointPath = new PathString("/authorize")
                TokenEndpointPath = new PathString("/token"), Provider = GlobalConfiguration.Configuration.DependencyResolver.GetRootLifetimeScope().Resolve<ClientAuthorizationServerProvider>(), AccessTokenProvider = GlobalConfiguration.Configuration.DependencyResolver.GetRootLifetimeScope().Resolve<AccessTokenAuthorizationServerProvider>(), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1), AuthenticationMode = AuthenticationMode.Active, //HTTPS is allowed only AllowInsecureHttp = false
#if DEBUG
 AllowInsecureHttp = true, #endif
 ApplicationCanDisplayErrors = true, }); /*
 //PasswordAuthorizationServerProvider app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions { //!!! // AccessTokenProvider= TokenEndpointPath = new PathString("/token"), //Provider = new ClientApplicationOAuthProvider(), //Provider = new PasswordAuthorizationServerProvider(), //Provider = DependencyInjectionConfig.container.Resolve<PasswordAuthorizationServerProvider>(), //Provider = DependencyResolver.Current.GetService<PasswordAuthorizationServerProvider>(), Provider = GlobalConfiguration.Configuration.DependencyResolver.GetRootLifetimeScope().Resolve<PasswordAuthorizationServerProvider>(), RefreshTokenProvider = GlobalConfiguration.Configuration.DependencyResolver.GetRootLifetimeScope().Resolve<RefreshAuthenticationTokenProvider>(), AccessTokenExpireTimeSpan = TimeSpan.FromHours(2), AuthenticationMode = AuthenticationMode.Active, //HTTPS is allowed only AllowInsecureHttp = false #if DEBUG AllowInsecureHttp = true, #endif }); */

            //app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); //app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }

集成Autofac async

 //註冊 Password Grant 受權服務
              builder.RegisterType<PasswordAuthorizationServerProvider>().AsSelf().SingleInstance(); builder.RegisterType<RefreshAuthenticationTokenProvider>().AsSelf().SingleInstance(); //註冊 Credential Grant Password 
            builder.RegisterType<ClientAuthorizationServerProvider>().AsSelf().SingleInstance(); builder.RegisterType<AccessTokenAuthorizationServerProvider>().AsSelf().SingleInstance(); //在Autofac中註冊Redis的鏈接,並設置爲Singleton (官方建議保留Connection,重複使用) //builder.Register(r =>{ return ConnectionMultiplexer.Connect(DBSetting.Redis);}).AsSelf().SingleInstance();
            var container = builder.Build(); GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);

啓用不記名驗證 ide

public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Configure Web API to use only bearer token authentication.
 config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API 路由
 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }

服務端 函數

/// <summary>
    /// Client Credentials 受權 /// </summary>
    public class ClientAuthorizationServerProvider : OAuthAuthorizationServerProvider { /// <summary>
        /// 受權服務 /// </summary>
        private readonly IClientAuthorizationService _clientAuthorizationService; /// <summary>
        /// 帳戶服務 /// </summary>
        private readonly IAccountService _accountService; /// <summary>
        /// 構造函數 /// </summary>
        /// <param name="clientAuthorizationService">受權服務</param>
        /// <param name="accountService">用戶服務</param>
        public ClientAuthorizationServerProvider(IClientAuthorizationService clientAuthorizationService, IAccountService accountService) { _clientAuthorizationService = clientAuthorizationService; _accountService = accountService; } /// <summary>
        /// 驗證Client Credentials[client_id與client_secret] /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { //http://localhost:48339/token //grant_type=client_credentials&client_id=irving&client_secret=123456&scope=user order
            /*
 grant_type 授與方式(固定爲 「client_credentials」) client_id 分配的調用oauth的應用端ID client_secret 分配的調用oaut的應用端Secret scope 受權權限。以空格分隔的權限列表,若不傳遞此參數,表明請求用戶的默認權限 */
            //validate client credentials should be stored securely (salted, hashed, iterated)
            string clientId; string clientSecret; context.TryGetBasicCredentials(out clientId, out clientSecret); //驗證用戶名密碼
            var clientValid = await _clientAuthorizationService.ValidateClientAuthorizationSecretAsync(clientId, clientSecret); if (!clientValid) { //Flurl 404 問題 //context.Response.StatusCode = Convert.ToInt32(HttpStatusCode.OK); //context.Rejected();
 context.SetError(AbpConstants.InvalidClient, AbpConstants.InvalidClientErrorDescription); return; } //need to make the client_id available for later security checks
            context.OwinContext.Set<string>("as:client_id", clientId); context.Validated(clientId); } /// <summary>
        /// 客戶端受權[生成access token] /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context) { /*
 var client = _oauthClientService.GetClient(context.ClientId); claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, client.ClientName)); */
            //驗證權限
            int scopeCount = context.Scope.Count; if (scopeCount > 0) { string name = context.Scope[0].ToString(); } //默認權限
            var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType); //!!!
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.ClientId)); var props = new AuthenticationProperties(new Dictionary<string, string> { { "client_id",context.ClientId }, { "scope",string.Join(" ",context.Scope) } }); var ticket = new AuthenticationTicket(claimsIdentity, props); context.Validated(ticket); return base.GrantClientCredentials(context); } /// <summary>
        /// http://stackoverflow.com/questions/26357054/return-more-info-to-the-client-using-oauth-bearer-tokens-generation-and-owin-in
        /// My recommendation is not to add extra claims to the token if not needed, because will increase the size of the token and you will keep sending it with each request. As LeftyX advised add them as properties but make sure you override TokenEndPoint method to get those properties as a response when you obtain the toke successfully, without this end point the properties will not return in the response. /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) { context.AdditionalResponseParameters.Add(property.Key, property.Value); } return base.TokenEndpoint(context); } }

Token生成與驗證 優化

/// <summary>
    /// 生成與驗證Token /// </summary>
    public class AccessTokenAuthorizationServerProvider : AuthenticationTokenProvider { /// <summary>
        /// 受權服務 /// </summary>
        private readonly IClientAuthorizationService _clientAuthorizationService; /// <summary>
        /// 構造函數 /// </summary>
        /// <param name="clientAuthorizationService">受權服務</param>
        public AccessTokenAuthorizationServerProvider(IClientAuthorizationService clientAuthorizationService) { _clientAuthorizationService = clientAuthorizationService; } //<summary> //建立Token //</summary> //<param name="context">上下文</param> //<returns></returns>
        public override async Task CreateAsync(AuthenticationTokenCreateContext context) { if (string.IsNullOrEmpty(context.Ticket.Identity.Name)) return; string IpAddress = context.Request.RemoteIpAddress + ":" + context.Request.RemotePort; var token = new Token() { ClientId = context.Ticket.Identity.Name, ClientType = "client_credentials", Scope = context.Ticket.Properties.Dictionary["scope"], UserName = context.Ticket.Identity.Name, IssuedUtc = DateTime.Parse(context.Ticket.Properties.IssuedUtc.ToString()), ExpiresUtc = DateTime.Parse(context.Ticket.Properties.IssuedUtc.ToString()), IpAddress = IpAddress }; token.AccessToken = context.SerializeTicket(); token.RefreshToken = string.Empty;//await _clientAuthorizationService.GenerateOAuthClientSecretAsync(); //Token沒有過時的狀況強行刷新,刪除老的Token保存新的Token
            if (await _clientAuthorizationService.SaveTokenAsync(token)) { context.SetToken(token.AccessToken); } } //<summary> //驗證Token //</summary> //<param name="context">上下文</param> //<returns></returns>
        public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { var request = new OAuthRequestTokenContext(context.OwinContext, context.Token); var ticket = new AuthenticationTicket(new ClaimsIdentity(), new AuthenticationProperties() { IssuedUtc = DateTime.UtcNow.AddYears(-1), ExpiresUtc = DateTime.UtcNow.AddYears(-1) }); if (request == null || request.Token.IsNullOrEmpty()) { context.SetTicket(ticket); } //驗證Token是否過時
            var vaild = await _clientAuthorizationService.VaildOAuthClientSecretAsync();
            if (vaild) { context.SetTicket(ticket); } } }

有贊API文檔

無心看到有讚的API文檔:http://open.koudaitong.com/doc,大體分爲三個部分。ui

1.基於OAUTH2受權【幾種受權模式全實現】this

2.基於簽名的方式【HMAC】url

3.各類語言的SDK

大體設計比較規範,後續時間再參考規範基於ASP.NET WEBAPI 集成優化OAUTH2與HMAC部分。

相關文章
相關標籤/搜索