一、前言

上面分享了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_tokenrefresh_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_tokenaccess_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资源网关(受保护的资源)中直接通过保存下来的验证公钥进行验证,从而通过授权