Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用
一、前言
上面分享了IdentityServer4
两篇系列文章,核心主题主要是密码授权模式
及自定义授权模式
,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享IdentityServer4
的授权流程及refreshtoken
。
系列文章目录(没看过的先看这几篇文章再来阅读本文章):
为了继续保持IdentityServer4
系列博客分享上下文一致,我这里再把上回授权中心
拆分后的图贴出来,如图:
图中的授权中心
就是通过IdentityServer4
实现的授权服务中心,我下面就直接用授权中心
代替IdentityServer4
的授权服务来继续述说,也感谢大家对我的支持,一直阅读我的文章。
二、授权流程
2.1 客户端验证流程图
流程图中,客户端仅仅会到授权中心
请求一次,并拿到验证公钥返回给Api资源
拥有端,后面客户端再次尝试请求Api资源
时候就不会再到授权中心
去获取验证公钥,会直接用之前获取到的公钥
进行验证,验证通过则授权通过。
2.2 授权及刷新refresh_token 流程图
然而通过授权中心
获取到的access_token
是有有效时间的,如果失效则需要通过refresh_token
重新到授权中心
去刷新获取最新的access_token
,整体的流程图如下:
客户端携带上一次获取到的access_token
请求受保护的Api资源
时,通过公钥
进行验证时发现access_token
已经过期,则客户端再携带refresh_token
向授权中心
再次发起请求,刷新access_token
以获得最新的access_token
和refresh_token
,用最新的access_token
去获取受保护的Api资源
,这样可以减少客户端多次跳转登录授权页面,提高用户体验。
三、应用实战
说到例子,我这里不从零开始撸代码, 还是在之前的代码基础上继续改造代码,在原有的定义客户端的代码中新增刷新access_token
的相关配置,代码如下:
public static IEnumerable<Client> GetClients(){ return new List<Client> { new Client() { ClientId =OAuthConfig.UserApi.ClientId, AllowedGrantTypes = new List<string>() { 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设置为trueAllowedScopes= { OAuthConfig.UserApi.ApiName, StandardScopes.OfflineAccess,//如果要获取refresh_tokens ,必须在scopes中加上OfflineAccess},
授权中心
,完整代码如下:
OAuthMemoryData
代码如下:
/// <summary>/// /// </summary>public class OAuthMemoryData{ /// <summary> /// 资源 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName), }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client() { ClientId =OAuthConfig.UserApi.ClientId, AllowedGrantTypes = new List<string>() { 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, }, }; } /// <summary> /// 测试的账号和密码 /// </summary> /// <returns></returns> public static List<TestUser> GetTestUsers() { return new List<TestUser> { new TestUser() { SubjectId = "1", Username = "test", Password = "123456" }, }; } /// <summary> /// 微信openId 的测试用户 /// </summary> /// <returns></returns> public static List<TestUser> GetWeiXinOpenIdTestUsers() { return new List<TestUser> { 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<CookiePolicyOptions>(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<ClientStore>() .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() .AddExtensionGrantValidator<WeiXinOpenGrantValidator>();//添加微信端自定义方式的验证 #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 访问授权中心
试一试,如下图:
访问结果中已经包含了refresh_token
和access_token
等相关信息。
我们再来通过access_token
访问Api资源
(上两篇有相关代码,未阅读上两篇先去查阅)这里我就直接携带access_token
去访问,如图:
访问成功!!
我们再来刷新下refresh_token
,访问如图:
刷新refresh_token
成功。
我们到这里再来做一个小小的测试,测试上面的授权流程中的,第4,5 步,上面说到第4步主要是客户端第一次请求Api资源
时会向ids4
服务网关去请求获取验证公钥,
获取成功返回给Api资源
并存储在内存中,后续不再会到ids4
服务去获取验证公钥
我们把上面的授权中心
(ids4服务网关)停止运行,再来用之前的access_token
请求Api资源
,如下图:
现在已经确定授权中心
(ids4服务网关)确实停止了,不能访问了,那我们再来通过之前未过期的access_token
来请求Api资源
网关,结果如下图:
完美,请求还是成功,这完全证明:客户端请求Api资源网关(受保护的资源)时,第一次收到请求会到授权中心(ids4服务网关)获取验证公钥,并保持到内存中,后面的请求不会再到授权中心去获得验证公钥,而是Api资源网关(受保护的资源)中直接通过保存下来的验证公钥进行验证,从而通过授权。