基于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部分。

相关文章
相关标签/搜索