Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用
一、前言
上面分享了identityserver4
两篇系列文章,核心主题主要是密码授权模式
及自定义授权模式
,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享identityserver4
的授权流程及refreshtoken
。
系列文章目录(没看过的先看这几篇文章再来阅读本文章):
- asp.net core identityserver4 中的基本概念
- asp.net core 中identityserver4 授权中心之应用实战
- asp.net core 中identityserver4 授权中心之自定义授权模式
为了继续保持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设置为true allowedscopes= { 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资源网关(受保护的资源)中直接通过保存下来的验证公钥进行验证,从而通过授权。
下一篇: 笑场