Owin + WebApi + OAuth2 搭建授权模式(授权码模式 Part I)
绪
最近想要整理自己代码封装成库,也十分想把自己的设计思路贴出来让大家指正,奈何时间真的不随人意。
想要使用 OWIN 做中间件服务,该服务中包含 管线、授权 两部分。于是决定使用 webapi 、OAuth2 来做。
在搭建途中,几乎是步步遇坎,由于对 OAuth2 内部流转的不了解,在网上到处找大牛的文献介绍,也整理不少,最后贴出。
在捋顺出验证整个内部过程后,遇到了如何使用 js 来发送请求达到验证,以及解决了遇到的跨域问题。
目前仅整理出了 授权码模式 ,闲言少叙,说说自己的理解吧。
1. 授权码理论,此部分摘要网上介绍较为详细的贴图

1.1 结合例子来说,当我们与某网站进行合作,需要得到他们的授权信息,在双方协商后,确立了
1.1.1 http://127.0.0.1:10000 对方授权地址
1.1.2 grant_type : authorization_code 授权码模式
1.1.3 response_type : code 授权类型
1.1.4 client_id : lightxun 客户端ID
1.1.5 redirect_uri : http://localhost:58632 返回接收 authorization_code 的地址
1.1.6 state : login 状态,我用来做标识当前请求状态
1.2 当我们在某网站进行登录时,会可以快捷的使用QQ、微博等账号进行授权登录。那么我们第一步点击登录方式,页面会调转到 对方授权地址,同时携带以上参数,最终获得授权码,触发【A】Authorization Request
<a href="http://127.0.0.1:10000/authorize?grant_type=authorization_code&response_type=code&client_id=lightxun&redirect_uri=http://localhost:58632/&state=login" target="_blank">authorize</a>
1.2.1 在某网站后台授权中 首先进行验证被注册的重定向url, 此处我的做法,在其内部将传来的 client_id 与 之前协商的 client_id 进行对比,如无误,则通过验证之前协商的 redirect_uri,为了安全,防止钓鱼,该方法对应为 OpenAuthorizationServerProvider 下的 ValidateClientRedirectUri 方法。此类为继承于 OAuthAuthorizationServerProvider ,并重写其中几部重要的处理方法。
/// <summary>
/// 验证 redirect_uri, 用于验证被注册的跳转Url
/// </summary>
public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
//验证uri 为了安全,防钓鱼
if(context.ClientId == OpenAuthorizationClients.Client.Id)
{
//将传来的redirectUri 与 参数验证对比, 所以该参数最好取自数据库
context.Validated(OpenAuthorizationClients.Client.RedirectUri);
}
}
1.2.2 在通过了上面的方法验证后,会验证 authorization_code 请求,该方法对应为 OpenAuthorizationServerProvider 下的 ValidateAuthorizeRequest 方法
/// <summary>
/// 验证 authorization_code 的请求
/// </summary>
public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
// IsAuthorizationCodeGrantType : 如果“response_type”查询字符串参数为“code”,则为 True
// IsImplicitGrantType : 如果“response_type”查询字符串参数为“token”,则为 True
if (context.AuthorizeRequest.ClientId == OpenAuthorizationClients.Client.Id &&
(context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType))
{
// 满足以上条件, 标记为已验证
context.Validated();
}
else
{
context.Rejected();
}
}
1.2.3 接着开始处理 authorization_code 请求,来生成授权码,该过程当中整理了一下逻辑,通过 state 来判断当前请求的状态, 如果是 login 则证明需要登录,登录后会将state修改为 validate 并重新发送验证请求。如果是 validate 则说明已成功登录,可以生成授权码了。该方法对应为 OpenAuthorizationServerProvider 下的 AuthorizeEndpoint 方法
/// <summary>
/// 处理登录逻辑
/// <summary>
[HttpPost]
[Route("OAuth/Login")]
public Model.ApiResult Login([FromBody]dynamic obj)
{
///验证用户名密码 IOwinContext _context = (OwinContext)Request.Properties["MS_OwinContext"];
IOwinRequest _request = _context.Request;
IOwinResponse _response = _context.Response; string _redirectUri = HttpUtility.UrlDecode(_request.Headers["redirect_uri"]);
string _clientId = _request.Headers["client_id"];
string _host = _request.Host.Value; return new Model.ApiResult
{
Data = $"/authorize?grant_type=authorization_code&response_type=code&client_id={_clientId}&redirect_uri={_redirectUri}&state=validate",
Msg = "for test"
};
、}
/// <summary>
/// 生成 authorization_code(authorization code 授权方式)、生成 access_token (implicit 授权模式)
/// </summary>
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{ //implicit 授权方式
if (context.AuthorizeRequest.IsImplicitGrantType)
{
var identity = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
}
//authorization code 授权方式
else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
{
// 通过state 来判断, 是登录还是 已登录的获取 code阶段
switch (context.AuthorizeRequest.State)
{
//如果是登录状态, 则直接跳转, 进行账户验证
case "login":
context.Response.Redirect("http://" + context.Request.Host.Value + "/Page/OAuth/Login.html");
context.RequestCompleted();
break;
case "validate":
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"];
var identity = new ClaimsIdentity(new GenericIdentity(clientId, OAuthDefaults.AuthenticationType)); var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
})); await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));
context.RequestCompleted();
break;
default: break;
}
}
}
1.2.4 生成 authorization_code 并返回 , 该方法对应 OpenAuthorizationCodeProvider 下的 Create 方法。该类继承于 AuthenticationTokenProvider, 触发【B】Authorization Grant
/// <summary>
/// 生成 authorization_code
/// </summary>
public override void Create(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
1.3 在接收到授权码之后,我们携带授权码、授权类型、重定向地址,以及设置请求header中加入 Authorization 参数。去寻要 token。触发【C】Authorization Grant
1.3.1 发送请求 js 代码如下
$.ajax({
async: true,
type: 'post',
url: 'http://127.0.0.1:10000/token',
beforeSend: function(xhr){
xhr.setRequestHeader('Authorization', "Basic " + Base64_Encode("lightxun:shinichi"))
},
data: {
grant_type: 'authorization_code',
code: _code, //授权码
redirect_uri: "http://localhost:58632/"
},
dataType: 'json',
contentType: 'application/json;charset=utf-8',
success: function (data) {
_token = data.access_token;
_refreshToken = data.refresh_token;
}
});
1.3.2 后台接收请求处理,首先验证 client 身份信息(ClientId 及 ClientSecret),该方法对应 OpenAuthorizationServerProvider 下的 ValidateClientAuthentication
/// <summary>
/// 验证Client的身份(ClientId以及ClientSecret)
/// 验证 client 信息, 验证从Basic架构的请求头或Form表单提交过来的客户端凭证
/// </summary>
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
} if (clientId != OpenAuthorizationClients.Client.Id)
{
context.SetError("invalid_client", "client is not valid");
return;
}
context.Validated();
}
1.3.3 验证后,开始将 authorization_code 解析成 access_token,该方法对应 OpenAuthorizationCodeProvider 下的 Receive
/// <summary>
/// 由 authorization_code 解析成 access_token
/// </summary>
public override void Receive(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
1.3.4 验证 token,该方法对应 OpenAuthorizationServerProvider 下的 ValidateTokenRequest
/// <summary>
/// 验证 access_token 的请求
/// </summary>
public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
if (context.TokenRequest.IsAuthorizationCodeGrantType || context.TokenRequest.IsRefreshTokenGrantType)
{
context.Validated();
}
else
{
context.Rejected();
}
}
1.3.5 生成 token,该方法对应 OpenRefreshTokenProvider 下的 Create 。触发【D】Access Token
/// <summary>
/// 生成 refresh_token
/// </summary>
public override void Create(AuthenticationTokenCreateContext context)
{
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(60); context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_refreshTokens[context.Token] = context.SerializeTicket();
}
1.4 最后我们携带着 token 去请求资源即可。触发【E】Access Token 和 【F】Protected Resource
$.ajax({
async: true,
type: 'post',
url: 'http://127.0.0.1:10000/token',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', "Basic " + Base64_Encode("lightxun:shinichi"))
},
data: {
grant_type: 'refresh_token',
refresh_token: _refreshToken,
},
dataType: 'json',
contentType: 'application/json;charset=utf-8',
success: function (data) {
_token = data.access_token;
_refreshToken = data.refresh_token;
}
})
今天整理的有点儿多,还有许多没有写到位,后续慢慢补充,也会把全额代码贴出来,包括 OAuth 部分全额配置及代码。
期间参考过的大牛博文连接如下
https://www.cnblogs.com/xishuai/p/aspnet-webapi-owin-oauth2.html
https://www.cnblogs.com/YamatAmain/p/5029466.html
https://www.code996.cn/post/2018/token-front/
https://www.cnblogs.com/xizz/archive/2015/12/18/5056195.html
https://cloud.tencent.com/developer/article/1090017
https://cloud.tencent.com/developer/article/1340117
https://cloud.tencent.com/developer/article/1157890
https://cloud.tencent.com/developer/article/1096046
http://blogread.cn/it/article/7808?f=wb_blogread
---- 以下为跨域文献
http://jcblog.net.cn/2016/07/19/webapi%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%EF%BC%88cors%EF%BC%89%E9%85%8D%E7%BD%AE/
https://www.cnblogs.com/baiyunchen/p/5769884.html
Owin + WebApi + OAuth2 搭建授权模式(授权码模式 Part I)的更多相关文章
- OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)
授权方式3-密码模式(Resource Owner Password Credentials Grant) 密码模式(Resource Owner Password Credentials Grant ...
- IdentityServer4实现OAuth2.0四种模式之授权码模式
接上一篇:IdentityServer4实现OAuth2.0四种模式之隐藏模式 授权码模式隐藏码模式最大不同是授权码模式不直接返回token,而是先返回一个授权码,然后再根据这个授权码去请求token ...
- webapi框架搭建系列博客
webapi框架搭建系列博客 webapi框架搭建-创建项目(一) webapi框架搭建-创建项目(二)-以iis为部署环境的配置 webapi框架搭建-创建项目(三)-webapi owin web ...
- 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】
之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...
- Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)
oauth2 server 微服务授权中心, github源码 https://github.com/spring-cloud/spring-cloud-security 对微服务接口做一些权 ...
- Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端
目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...
- (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
随机推荐
- Android下数据库操作——增删改查
Android下数据库第一种方式增删改查 1.创建一个帮助类的对象,调用getReadableDatabase方法,返回一个SqliteDatebase对象 2.使用SqliteDat ...
- Apache——配置与应用
Apache配置与应用 1.概述 2.httpd服务支持的虚拟主机类型 3.构建虚拟Web主机 4.构建Web虚拟目录与用户授权限制 5.日志分割 6.AWStats分析系统 1.概述: 虚拟web主 ...
- Solution -「LOJ #6029」「雅礼集训 2017」市场
\(\mathcal{Description}\) Link. 维护序列 \(\lang a_n\rang\),支持 \(q\) 次如下操作: 区间加法: 区间下取整除法: 区间求最小值: 区 ...
- Java全栈学习路线、学习资源和面试题一条龙
肝了一个月,终于把Java学习路线.面试题资源和电子书资源都整理好了. Java 从基础到微服务的学习路线,其中还包括科班知识.学习建议.后续的学习引导和相应的学习资源(视频.书籍.网站),还整理了J ...
- 『无为则无心』Python面向对象 — 53、对Python中封装的介绍
目录 1.继承的概念 2.继承的好处 3.继承体验 4.单继承 5.多继承 1.继承的概念 在Python中,如果两个类存在父子级别的继承关系,子类中即便没有任何属性和方法,此时创建一个子类对象,那么 ...
- Linux下/目录 、/home目录 、~目录的区别
/ :根目录 ,所有目录的最顶层目录,从任何用户执行该命令都会进入同一个目录,即所有用户共享.如下图: /home:/下面的home目录,名为家目录,但是很多人叫为用户列表目录,因为/home下是这台 ...
- 【性能测试实战:jmeter+k8s+微服务+skywalking+efk】系列之:性能测试场景设计
说明: 本文是基于虚拟机环境配置设计的 性能测试需求 总tps≥100 每个业务的rt<500ms 持续稳定跑50万业务量 单场景 目的:找到单场景的性能问题,为容量场景提供参考,如果低于容量场 ...
- python数据分析与挖掘实战第二版pdf-------详细代码与实现
[书名]:PYTHON数据分析与挖掘实战 第2版[作者]:张良均,谭立云,刘名军,江建明著[出版社]:北京:机械工业出版社[时间]:2020[页数]:340[isbn]:9787111640028 学 ...
- 一图胜千言,想让数据产生影响力,必须拥有好上手的BI数据分析工具
当杂乱无章的数据,经过数据清洗后,得到了想用的数据,但是查看这些数据通过数据库只能看到数据本身,无法看到其中的规律,可以通过BI数据分析工具,图形化展示数据,使数据更形象化的展现在用户面前,更容易看出 ...
- 【windows 访问控制】十、词汇列表和对应C#类、枚举、命名空间
principals:主体 主体包含标识(identity 对用来来说就是用户名,对程序来说就是SID)和用户角色(role 对用户来说就是 组名 对程序来说就是组SID)subject:主体.主语i ...