基于OWIN ASP.NET WebAPI 使用OAUTH2授权服务的几点优化
前面在ASP.NET WEBAPI中集成了Client Credentials Grant与Resource Owner Password Credentials Grant两种OAUTH2模式,今天在调试Client Credentials Grant想到如下几点
- 建议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是否有效
服务实现
配置Startup
/// <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
//注册 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);
启用不记名验证
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,大致分为三个部分。
1.基于OAUTH2授权【几种授权模式全实现】
2.基于签名的方式【HMAC】
3.各种语言的SDK
大致设计比较规范,后续时间再参考规范基于ASP.NET WEBAPI 集成优化OAUTH2与HMAC部分。
基于OWIN ASP.NET WebAPI 使用OAUTH2授权服务的几点优化的更多相关文章
- 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】
之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...
- ASP.NET WebAPI构建API接口服务实战演练
一.课程介绍 一.王小二和他领导的第一次故事 有一天王小二和往常一下去上早班,刚吃完早餐刚一打开电脑没一会儿.王小二的领导宋大宝走到他的面前,我们现在的系统需要提供服务给其他内部业务系统,我看你平时喜 ...
- (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】
适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token
OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮 ...
- WebApi Owin SelfHost OAuth2 - 授权服务和资源服务分离方案
使用JWT 参考:http://www.cnblogs.com/grissom007/p/6294746.html
- 一站式WebAPI与认证授权服务
保护WEBAPI有哪些方法? 微软官方文档推荐了好几个: Azure Active Directory Azure Active Directory B2C (Azure AD B2C)] Ident ...
- Owin中间件搭建OAuth2.0认证授权服务体会
继两篇转载的Owin搭建OAuth 2.0的文章,使用Owin中间件搭建OAuth2.0认证授权服务器和理解OAuth 2.0之后,我想把最近整理的资料做一下总结. 前两篇主要是介绍概念和一个基本的D ...
随机推荐
- 日志分析工具ELK配置详解
日志分析工具ELK配置详解 一.ELK介绍 1.1 elasticsearch 1.1.1 elasticsearch介绍 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分 ...
- LINUX内核分析期末总结
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.课程总结 1 ...
- .NET/C#/Oracle数据库操作类
public static class OracleHelper { //数据库连接字符串 private readonly static string connstr = Configuration ...
- [转载] 3. JebAPI 之 jeb.api.ast
本文转载自: https://www.zybuluo.com/oro-oro/note/143651 0. 序 Jeb 本身是支持变量重命名的,所以,混淆了的变量名.类名可以修改. 实际上,它还可以做 ...
- Selenium2+python自动化3-解决pip使用异常
一.pip出现异常 有一小部分童鞋在打开cmd输入pip后出现下面情况:Did not provide a commandDid not provide a command?这是什么鬼?正常情况应该是 ...
- Daily Scrum 12.1
今日完成任务: 完成了对源代码结构的修改,删除冗余等:和其他小组讨论了关于整合的问题,向其他小组介绍自己小组使用的数据库等. 明日任务: 晏旭瑞 初步完成文档上传下载 孙思权 深入了解数据库中每个表, ...
- Spring 4 官方文档学习(十一)Web MVC 框架之约定优于配置
当返回一个ModelAndView时,可以使用其addObject(Object obj)方法,此时的约定是: An x.y.User instance added will have the nam ...
- live555 直播arm-linux视频
live555例程testOnDemandRTSPServer.cpp启动一个流服务器 首先启动使用环境, TaskScheduler* scheduler = BasicTaskScheduler: ...
- Linux下设置网卡随系统启动
在GUI下安装RHEL,在配置网卡的时候,有时候会忘了勾选网卡随系统自动启动,解决方法是系统启动后,打开网卡配置文件/etc/sysconfig/network-script/ifcfg-eth*,将 ...
- 6本Android开发必备图书
学习一样新事物或许有多种方式,报培训班,看视频,向高手请教等等,但一本好书往往可以让你少走很多弯路,事半功倍.今天与大家分享一些Android开发值得一读的书籍,希望对新手安卓开发者们有所帮助. 1. ...