Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用
Jlion 人气:0
## 一、前言
上面分享了`IdentityServer4` 两篇系列文章,核心主题主要是`密码授权模式`及`自定义授权模式`,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享`IdentityServer4`的授权流程及`refreshtoken`。
系列文章目录(**没看过的先看这几篇文章再来阅读本文章**):
- [Asp.Net Core IdentityServer4 中的基本概念](https://www.cnblogs.com/jlion/p/12437441.html)
- [Asp.Net Core 中IdentityServer4 授权中心之应用实战](https://www.cnblogs.com/jlion/p/12447081.html)
- [Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式](https://www.cnblogs.com/jlion/p/12468365.html)
为了继续保持`IdentityServer4` 系列博客分享上下文一致,我这里再把上回`授权中心`拆分后的图贴出来,如图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200315225158795-1178949567.png)
图中的`授权中心`就是通过`IdentityServer4`实现的授权服务中心,我下面就直接用`授权中心`代替`IdentityServer4`的授权服务来继续述说,也感谢大家对我的支持,一直阅读我的文章。
## 二、授权流程
### 2.1 客户端验证流程图
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200315224551855-1158236720.png)
流程图中,客户端仅仅会到`授权中心` 请求一次,并拿到验证公钥返回给`Api资源`拥有端,后面客户端再次尝试请求`Api资源`时候就不会再到`授权中心`去获取验证公钥,会直接用之前获取到的`公钥`进行验证,验证通过则授权通过。
### 2.2 授权及刷新refresh_token 流程图
然而通过`授权中心` 获取到的`access_token` 是有有效时间的,如果失效则需要通过`refresh_token` 重新到`授权中心`去刷新获取最新的`access_token`,整体的流程图如下:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316214849214-1093908921.jpg)
客户端携带上一次获取到的`access_token` 请求受保护的`Api资源`时,通过`公钥`进行验证时发现`access_token`已经过期,则客户端再携带`refresh_token` 向`授权中心`再次发起请求,刷新`access_token`以获得最新的`access_token`和`refresh_token`,用最新的`access_token` 去获取受保护的`Api资源`,这样可以减少客户端多次跳转登录授权页面,提高用户体验。
## 三、应用实战
说到例子,我这里不从零开始撸代码, 还是在之前的代码基础上继续改造代码,在原有的定义客户端的代码中新增刷新`access_token`的相关配置,代码如下:
```
public static IEnumerable GetClients()
{
return new List
{
new Client()
{
ClientId =OAuthConfig.UserApi.ClientId,
AllowedGrantTypes = new List()
{
GrantTypes.ResourceOwnerPassword.FirstOrDefault(),//Resource Owner Password模式
GrantTypeConstants.ResourceWeixinOpen,
},
ClientSecrets = {new Secret(OAuthConfig.UserApi.Secret.Sha256()) },
AllowOfflineAccess = true,//如果要获取refresh_tokens ,必须把AllowOfflineAccess设置为true
AllowedScopes= {
OAuthConfig.UserApi.ApiName,
StandardScopes.OfflineAccess,
},
AccessTokenLifetime = OAuthConfig.ExpireIn,
},
};
}
```
如果你需要刷新`access_token`,则需要把`AllowOfflineAccess`设置`true`,同时添加`StandardScopes.OfflineAccess` 这个`Scopes`,主要代码如下:
```
AllowOfflineAccess = true,//如果要获取refresh_tokens ,必须把AllowOfflineAccess设置为true
AllowedScopes= {
OAuthConfig.UserApi.ApiName,
StandardScopes.OfflineAccess,//如果要获取refresh_tokens ,必须在scopes中加上OfflineAccess
},
```
`授权中心`,完整代码如下:
`OAuthMemoryData` 代码如下:
```
///
///
///
public class OAuthMemoryData
{
///
/// 资源
///
///
public static IEnumerable GetApiResources()
{
return new List
{
new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName),
};
}
public static IEnumerable GetClients()
{
return new List
{
new Client()
{
ClientId =OAuthConfig.UserApi.ClientId,
AllowedGrantTypes = new List()
{
GrantTypes.ResourceOwnerPassword.FirstOrDefault(),//Resource Owner Password模式
GrantTypeConstants.ResourceWeixinOpen,
},
ClientSecrets = {new Secret(OAuthConfig.UserApi.Secret.Sha256()) },
AllowOfflineAccess = true,//如果要获取refresh_tokens ,必须把AllowOfflineAccess设置为true
AllowedScopes= {
OAuthConfig.UserApi.ApiName,
StandardScopes.OfflineAccess,
},
AccessTokenLifetime = OAuthConfig.ExpireIn,
},
};
}
///
/// 测试的账号和密码
///
///
public static List GetTestUsers()
{
return new List
{
new TestUser()
{
SubjectId = "1",
Username = "test",
Password = "123456"
},
};
}
///
/// 微信openId 的测试用户
///
///
public static List GetWeiXinOpenIdTestUsers()
{
return new List
{
new TestUser(){
SubjectId="owerhwroogs3902openId",
}
};
}
}
```
`Startup` 完整代码如下:
```
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
#region 内存方式
//services.AddIdentityServer()
// .AddDeveloperSigningCredential()
// .AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
// .AddInMemoryClients(OAuthMemoryData.GetClients())
// .AddTestUsers(OAuthMemoryData.GetTestUsers());
#endregion
#region 数据库存储方式
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
//.AddInMemoryClients(OAuthMemoryData.GetClients())
.AddClientStore()
.AddResourceOwnerValidator()
.AddExtensionGrantValidator();//添加微信端自定义方式的验证
#endregion
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
```
`授权中心`代码基本上已经改造完成,我们用postman 访问`授权中心` 试一试,如下图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316224158908-1620586984.png)
访问结果中已经包含了`refresh_token`和`access_token`等相关信息。
我们再来通过`access_token` 访问`Api资源`(上两篇有相关代码,未阅读上两篇先去查阅)这里我就直接携带`access_token`去访问,如图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316225217253-1556797160.png)
访问成功!!
我们再来刷新下`refresh_token` ,访问如图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316225352851-1579605608.png)
刷新`refresh_token`成功。
**我们到这里再来做一个小小的测试,测试上面的授权流程中的,第4,5 步,上面说到第4步主要是客户端第一次请求`Api资源`时会向`ids4`服务网关去请求获取验证公钥,
获取成功返回给`Api资源`并存储在内存中,后续不再会到`ids4`服务去获取验证公钥**
我们把上面的`授权中心` (ids4服务网关)停止运行,再来用之前的`access_token`请求`Api资源`,如下图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316230013192-14329478.png)
现在已经确定`授权中心`(ids4服务网关)确实停止了,不能访问了,那我们再来通过之前未过期的`access_token`来请求`Api资源`网关,结果如下图:
![](https://img2020.cnblogs.com/blog/824291/202003/824291-20200316230121991-1846428283.png)
完美,请求还是成功,这完全证明:**客户端请求Api资源网关(受保护的资源)时,第一次收到请求会到授权中心(ids4服务网关)获取验证公钥,并保持到内存中,后面的请求不会再到授权中心去获得验证公钥,而是Api资源网关(受保护的资源)中直接通过保存下来的验证公钥进行验证,从而通过授权**。
加载全部内容