第四十五节:复习Session/Jwt原理、Jwt实操、Swagger中配置Jwt、Jwt撤回方案、双token方案

一. 复习

1. 旧的Session校验机制

  (https://www.cnblogs.com/yaopengfei/p/10435032.html)

 

2. Session原理

(https://www.cnblogs.com/yaopengfei/p/8057176.html)

 

3. Jwt原理

(重点参考:https://www.cnblogs.com/yaopengfei/p/12162507.html)

  样式:"xxxxxxxxxxxx.xxxxxxxxxxxxx.xxxxxxxxxxxxxxxx"由三部分组成.

(1).Header头部:{\"alg\":\"HS256\",\"typ\":\"JWT\"}基本组成,也可以自己添加别的内容,然后对最后的内容进行Base64编码.

(2).Payload负载:iss、sub、aud、exp、nbf、iat、jti基本参数,也可以自己添加别的内容,然后对最后的内容进行Base64编码.

(3).Signature签名:将Base64后的Header和Payload通过.组合起来,然后利用【Hmacsha256+密钥】进行加密, 形成的字符串作为第三部分。

 

二. 实操

1. 加密和解密测试

  这里基于 【System.IdentityModel.Tokens.Jwt】程序集测试

  下面代码,解密的时候不验证 aud 和 iss, ClockSkew = TimeSpan.Zero  代表校验过期时间的偏移量,即验证过期时间:(expires+该值),该值默认为5min,这里设置为0,表示生成token时的expries即为过期时间

 /// <summary>
        /// 测试加密和解密
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public string TestJwAndJm()
        {
            string secretKey = configuration["SecretKey"];
            string token;
            //加密
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.Default.GetBytes(secretKey);
                var tokenDescriptor = new SecurityTokenDescriptor()
                {
                    Subject = new ClaimsIdentity(new Claim[] {
                         new Claim("userId","00000000001"),
                         new Claim("userAccount","admin")
                    }),
                    Expires = DateTime.UtcNow.AddSeconds(10),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
                token = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));           //将组装好的格式生成加密后的jwt字符串
                Console.WriteLine("加密生成的token为:" + token);
            }
            //解密
            bool result;
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.Default.GetBytes(secretKey);
                var validationParameters = new TokenValidationParameters
                {
                    ValidateAudience = false, //表示不验证aud
                    ValidateIssuer = false,   //表示不验证iss
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ClockSkew = TimeSpan.Zero   //代表校验过期时间的偏移量,即验证过期时间:(expires+该值),该值默认为5min,这里设置为0,表示生成token时的expries即为过期时间
                };
                SecurityToken validatedToken;   //解密后的对象
                try
                {
                    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
                    result = true;
                    //获取payload中的数据
                    var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson();
                    Console.WriteLine("解密后的内容为:" + jwtPayload);
                }
                catch (SecurityTokenExpiredException)
                {
                    //表示过期
                    result = false;
                }
                catch (SecurityTokenException)
                {
                    //表示token错误
                    result = false;
                }
            }

            return $"token:{token}, result:{result}";
        }

2. 在webapi中测试

  这里基于【Microsoft.AspNetCore.Authentication.JwtBearer】程序集测试

 (1). 编写获取token的接口 GetToken()

 /// <summary>
        /// 获取Token
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public String GetToken()
        {
            string secretKey = configuration["SecretKey"];
            string token;
            //加密
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.Default.GetBytes(secretKey);
                var tokenDescriptor = new SecurityTokenDescriptor()
                {
                    Subject = new ClaimsIdentity(new Claim[] {
                         new Claim("userId","00000000001"),
                         new Claim("userAccount","admin")
                    }),
                    Expires = DateTime.UtcNow.AddMinutes(5),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
                token = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));           //将组装好的格式生成加密后的jwt字符串
            }
            return token;

        }

 (2). 通过services.AddAuthentication("Bearer").AddJwtBearer() 注册jwt校验

//注册jwt校验
builder.Services.AddAuthentication("Bearer").AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,//是否验证Issuer
        ValidateAudience = false,//是否验证Audience
        ClockSkew = TimeSpan.Zero,//校验时间是否过期时,设置的时钟偏移量(默认是5min,这里设置为0,即用的是产生token时设置的过期时间)
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(builder.Configuration["SecretKey"])),//拿到SecurityKey
    };
});

 (3). 开启认证中间件,app.UseAuthentication();

 

 注意:必须在授权中间件UseAuthorization上面

 (4). 编写两个接口 GetMsg1() GetMsg2(), 其中GetMsg1接口上添加 [Authorize] 表示开启jwt校验, GetMsg2不开启

        /// <summary>
        /// 测试Jwt校验
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public string GetMsg1()
        {
            return "请求成功";
        }

        [HttpPost]
        public string GetMsg2()
        {
            return "请求成功";
        } 

  测试1:分别请求GetMsg1和GetMsg2接口,其中GetMsg1接口报401没有权限, 402接口则正常请求成功

  测试2:

     A. 先请求GetToken接口获取token

     B. 通过postMan请求GetMsg1接口, 并且配置 Bearer Token, 请求成功.  详见:doc中的截图

 

 

3. swagger中配置jwt

  背景:开启jwt校验后,swagger中无法请求接口了

  解决方案:

   (1). 在AddSwaggerGen中添加开启输入jwt校验的代码

//给swagger中配置开启jwt输入
builder.Services.AddSwaggerGen(c =>
{
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "Bearer认证, 即:说白了就是在Header中传递参数的时候('Authorization', 'Bearer ' + token),在值的前面加了一个Bearer和空格,然后在解析的时候需要隔离拿出来token值.",
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "Authorization"
        },
        Scheme = "oauth2",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
    };
    c.AddSecurityDefinition("Authorization", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    c.AddSecurityRequirement(requirement);
});

PS:通常我更习惯auth认证

builder.Services.AddSwaggerGen(options =>
{
    //1. 通过反射开启注释
    var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));

    //2. 支持jwt传递
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "普通的jwt校验, 即:说白了就是在Header中传递参数的时候多了一个:auth=token",
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "auth"
        },
        Scheme = "oauth2",
        Name = "auth",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
    };
    options.AddSecurityDefinition("auth", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    options.AddSecurityRequirement(requirement);
});

 (2). 运行后,进入swagger页面,右上角会出现一个Authorize的按钮, 点击后进入, 输入token

   (3). 重新请求getMsg1接口,就会自动携带token进行请求,该接口请求成功

 

 

 

 

三. Jwt撤回问题

1.需求

   比如用户被删除了、禁用了; jwt被盗用了; 单设备登录等场景, 由于jwt是有有效期的, 所以经常会出现了前面的场景后, token还没失效,也就是说还能正常使用, 那么我现在的, 需求就是当出现这些场景,可以手动的控制jwt过期问题

2.解决方案1【不做探讨】

  把所有生成的jwt都在服务器上存一份(可以存放到redis里), 然后每次比较都要先查询一下redis里是否存在请求传过来的jwt,然后再进行准确性校验,另外可以手动控制删除redis中想让失效的jwt。

3 .解决方案2【推荐】

   在用户表中增加一个整数类型的列JWTVersion,代表最后一次发放出去的令牌的版本号;每次登录成功,发放令牌的时候,都让JWTVersion的值自增,同时将JWTVersion的值也放到JWT令牌的负载payLoad中; 当执行禁用用户,撤回用户的令牌等操作的时候,把这个用户对应的JWTVersion列的值自增即可; 当服务器端收到客户端提交的JWT令牌后,先把JWT令牌中的JWTVersion值和数据库中JWTVersion的值做一下比较,如果JWT令牌中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT令牌过期了。

 【补充:这套方案也适用于同一个账号不能同时访问系统的需求!!!】

4. 版本1:直接从DB中拿jwtVersion获取

思路:

 (1). 编写登录接口CheckLogin, 登录成功后, 将jwtVerson++, 并将其存放到jwt的payload负载中

 /// <summary>
        /// 登录接口
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> CheckLogin(UserModel user)
        {

            //1.校验登录
            var userData = dbContext.Set<UserInfo>().Where(u => u.userAccount == user.userAccount && u.userPwd == user.userPwd).FirstOrDefault();
            if (userData != null)
            {
                //1.1登录成功jwtVersion需要自增1
                userData.jwtVersion++;

                string secretKey = configuration["SecretKey"];
                //1.2加密
                //额外的header参数也可以不设置
                var extraHeaders = new Dictionary<string, object>
                    {
                         {"myName", "limaru" },
                    };
                //过期时间(可以不设置,下面表示签名后 20分钟过期)
                double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
                var payload = new Dictionary<string, object>
                    {
                         {"userId", userData.id},
                         {"userAccount", userData.userAccount },
                         {"jwtVersion", userData.jwtVersion.ToString() },
                         {"exp",exp }
                    };

                //1.3 进行JWT签名
                var token = JWTHelp.JWTJiaM(payload, secretKey, extraHeaders);

                //1.4 保存数据库
                _ = await dbContext.SaveChangesAsync();

                return Ok(new { status = "ok", msg = "登录成功", token });

            }
            else
            {
                return Ok(new { status = "error", msg = "登录失败" });
            }

        }

 (2).编写过滤器:CheckJwt

     A. 先校验是否有Skip标签   (为了保证完整性,该案例并无作用)

     B. 校验JWT自身的准确性(非空、过期、错误等)

     C. JWT自身校验通过后, 从jwt解密字符串中拿到jwtVersion、UserId ,根据UserId去数据库中查询JwtVersion, 如果为空, 校验直接不通过

     D. 如果DB中的JwtVersion不为空,且客户端的版本 >= DB中的版本号,则表示校验通过; 反之检验失败

代码分享:

 /// <summary>
    /// 版本1--纯DB操作
    /// </summary>
    public class CheckJwt : IAsyncActionFilter
    {
        private readonly IConfiguration configuration;

        private readonly Core6xDBContext dbContext;
        public CheckJwt(IConfiguration configuration, Core6xDBContext dbContext)
        {
            this.configuration = configuration;
            this.dbContext = dbContext;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {

            //1. 先判断是否有skip跳过标签
            var isSkip = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAttribute));
            if (isSkip)
            {
                await next();
                return;
            }

            //2. 校验jwt自身的准确性
            var token = context.HttpContext.Request.Headers["auth"].ToString();    //ajax请求传过来
            if (token == "null" || string.IsNullOrEmpty(token))
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数为空" });
                return;
            }
            var result = JWTHelp.JWTJieM(token, configuration["SecretKey"]);
            if (result == "expired")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数已经过期" });
                return;
            }
            else if (result == "invalid")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else if (result == "error")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else
            {
                //3. 表示校验通过,接下来校验jwtVersion的准确性
                var jwtModel = JsonConvert.DeserializeObject<JwtModel>(result);

                //3.1 获取数据库中该用户的jwtVersion
                long? myJwtVersion = dbContext.Set<UserInfo>().Where(u => u.id == jwtModel.userId).Select(u => u.jwtVersion).FirstOrDefault();
                if (myJwtVersion==null)
                {
                    context.Result = new JsonResult(new { status = "error", msg = "该用户不存在" });
                    return;
                }

                //3.2 客户端提交的版本 大于等于 DB的版本号, 验证通过 (客户端jwtVerson小于DB中的版本号, 则说明过期了)

                if (long.Parse(jwtModel.jwtVersion) >= myJwtVersion)
                {
                    //表示校验通过,执行action中的业务
                    context.RouteData.Values.Add("auth", result);
                    await next();
                }
                else
                {
                    context.Result = new JsonResult(new { status = "error", msg = "jwt版本号错误" });
                    return;
                }

            }


        }
View Code

 测试:

    访问CheckLogin获取token, 然后携带token访问GetMsg接口,此时请求成功; 然后去DB中找到这个用户将jwtVersion增加1后,再次访问GetMsg接口,则访问不通过,提示jwtVerson版本号错误

        /// <summary>
        /// 版本1的验证
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [TypeFilter(typeof(CheckJwt))]
        public string GetMsg()
        {
            return "恭喜你,访问成功了";

        }

如下图

 剖析:

    上述方案在CheckJwt中过滤器中每次都要访问DB,性能很差, 可以考虑引入内存缓存来处理这个问题,提供性能.

     

5 版本2:引入内存缓存进行优化 【推荐】

思路:

    使用IMemoryCache中的GetOrCreate方法,表示缓存存在,直接从缓存中读取内容并返回;缓存不存在,执行数据库读取操作→写入缓存→返回内容, 从而缓解了DB的压力

代码分享:

 /// <summary>
    /// 版本2--引入内存缓存
    /// </summary>
    public class CheckJwt2 : IAsyncActionFilter
    {
        private readonly IConfiguration configuration;

        private readonly Core6xDBContext dbContext;

        private readonly IMemoryCache cache;
        public CheckJwt2(IConfiguration configuration, Core6xDBContext dbContext, IMemoryCache cache)
        {
            this.configuration = configuration;
            this.dbContext = dbContext;
            this.cache = cache;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {

            //1. 先判断是否有skip跳过标签
            var isSkip = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAttribute));
            if (isSkip)
            {
                await next();
                return;
            }

            //2. 校验jwt自身的准确性
            var token = context.HttpContext.Request.Headers["auth"].ToString();    //ajax请求传过来
            if (token == "null" || string.IsNullOrEmpty(token))
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数为空" });
                return;
            }
            var result = JWTHelp.JWTJieM(token, configuration["SecretKey"]);
            if (result == "expired")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数已经过期" });
                return;
            }
            else if (result == "invalid")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else if (result == "error")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else
            {
                //3. 表示校验通过,接下来校验jwtVersion的准确性
                var jwtModel = JsonConvert.DeserializeObject<JwtModel>(result);

                //3.1 获取数据库中该用户的jwtVersion 【此处引入缓存】
                string cacheKey = $"JwtVersionCheck_{jwtModel.userId}";
                //GetOrCreate用法:缓存存在,直接从缓存中读取内容并返回;缓存不存在,执行数据库读取操作→写入缓存→返回内容
                long? myJwtVersion = cache.GetOrCreate(cacheKey, opt =>
                {
                    opt.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(40);  //设置绝对过期时间为20s
                    return dbContext.Set<UserInfo>().Where(u => u.id == jwtModel.userId).Select(u => u.jwtVersion).FirstOrDefault();
                });
                if (myJwtVersion == null)
                {
                    context.Result = new JsonResult(new { status = "error", msg = "该用户不存在" });
                    return;
                }

                //3.2 客户端提交的版本 大于等于 DB的版本号, 验证通过 (客户端jwtVerson小于DB中的版本号, 则说明过期了)
                if (long.Parse(jwtModel.jwtVersion) >= myJwtVersion)
                {
                    //表示校验通过,执行action中的业务
                    context.RouteData.Values.Add("auth", result);
                    await next();
                }
                else
                {
                    context.Result = new JsonResult(new { status = "error", msg = "jwt版本号错误" });
                    return;
                }

            }


        }
    }
View Code

 测试:

       /// <summary>
        /// 版本2的验证
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [TypeFilter(typeof(CheckJwt2))]
        public string GetMsg2()
        {
            return "恭喜你,访问成功了";

        }

    访问CheckLogin获取token, 然后携带token访问GetMsg2接口,此时请求成功; 然后去DB中找到这个用户将jwtVersion增加1后,再次访问GetMsg2接口,由于缓存的过期时间为20s,还没有过期,

    读取的JwtVersion是修改前的值,所以还是能访问通过;等待20s过后,再次访问GetMsg2接口,则访问不通过,提示jwtVerson版本号错误

 剖析:

    (1).虽然引入内存缓存可以缓解DB的压力,但是有利就有弊,在缓存没有过期的这段时间里,手动增加DB中的JwtVersion,是无法让客户端jwt失效的,当然通常缓存过期时间设置的不长,该问题也无可厚非,关系不大的.

    (2).集群环境中,由于内存缓存等导致的并发问题,假如集群的A服务器中缓存保存的还是版本为5的数据,但客户端提交过来的可能已经是版本号为6的数据。     因此只要是客户端提交的版本号>=服务器上取出来(可能是从Db,也可能是从缓存)的版本号,那么也是可以的

 

6 版本3:使用Redis存储JwtVersion【非常推荐】

思路:

 将JwtVerson存放到Redis里, 即可以环境DB压力,也可以解决集群环境下内存缓存的局限性

 步骤:

   (1). 基于【CSRedisCore3.8.3】程序集进行redis访问,这里采用简单粗暴的写法  (详细可以参考:https://www.cnblogs.com/yaopengfei/p/14211883.html)

   (2). 在登录方法CheckLogin3中将 对该用户jwtVersion对应的key进行自增1, 然后将自增后的值写入payLoad中

 /// <summary>
        /// 登录接口--版本3使用
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> CheckLogin3(UserModel user)
        {

            //1.校验登录
            var userData = dbContext.Set<UserInfo>().Where(u => u.userAccount == user.userAccount && u.userPwd == user.userPwd).FirstOrDefault();
            if (userData != null)
            {
                //1.1登录成功redis里的jwtVersion需要自增1
                var rds = new CSRedis.CSRedisClient(configuration["RedisStr"]);
                string userKey = $"userInfo_{userData.id}";
                var jwtVersion = rds.IncrBy(userKey);  //获取自增后的jwtVersion


                string secretKey = configuration["SecretKey"];
                //1.2加密
                //额外的header参数也可以不设置
                var extraHeaders = new Dictionary<string, object>
                    {
                         {"myName", "limaru" },
                    };
                //过期时间(可以不设置,下面表示签名后 20分钟过期)
                double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
                var payload = new Dictionary<string, object>
                    {
                         {"userId", userData.id},
                         {"userAccount", userData.userAccount },
                         {"jwtVersion", jwtVersion.ToString() },
                         {"exp",exp }
                    };

                //1.3 进行JWT签名
                var token = JWTHelp.JWTJiaM(payload, secretKey, extraHeaders);

                //1.4 保存数据库
                _ = await dbContext.SaveChangesAsync();

                return Ok(new { status = "ok", msg = "登录成功", token });
            }
            else
            {
                return Ok(new { status = "error", msg = "登录失败" });
            }
        }

   (3). 编写过滤器CheckJwt3, 与版本1的逻辑相同,只不过改为从redis中读取jwtVersion了

代码分享:

/// <summary>
    /// 版本3--基于Redis存储jwtVersion
    /// </summary>
    public class CheckJwt3 : IAsyncActionFilter
    {
        private readonly IConfiguration configuration;

        private readonly Core6xDBContext dbContext;
        public CheckJwt3(IConfiguration configuration, Core6xDBContext dbContext)
        {
            this.configuration = configuration;
            this.dbContext = dbContext;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {

            //1. 先判断是否有skip跳过标签
            var isSkip = context.ActionDescriptor.EndpointMetadata.Any(x => x.GetType() == typeof(SkipAttribute));
            if (isSkip)
            {
                await next();
                return;
            }

            //2. 校验jwt自身的准确性
            var token = context.HttpContext.Request.Headers["auth"].ToString();    //ajax请求传过来
            if (token == "null" || string.IsNullOrEmpty(token))
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数为空" });
                return;
            }
            var result = JWTHelp.JWTJieM(token, configuration["SecretKey"]);
            if (result == "expired")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,参数已经过期" });
                return;
            }
            else if (result == "invalid")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else if (result == "error")
            {
                context.Result = new JsonResult(new { status = "error", msg = "非法请求,未通过校验" });
                return;
            }
            else
            {
                //3. 表示校验通过,接下来校验jwtVersion的准确性
                var jwtModel = JsonConvert.DeserializeObject<JwtModel>(result);

                //3.1 获取Redis该用户的jwtVersion
                var rds = new CSRedis.CSRedisClient(configuration["RedisStr"]);
                string userKey = $"userInfo_{jwtModel.userId}";
                long? myJwtVersion = rds.Get<long?>(userKey);

                if (myJwtVersion==null)
                {
                    context.Result = new JsonResult(new { status = "error", msg = "该用户不存在" });
                    return;
                }

                //3.2 客户端提交的版本 大于等于 Redis的版本号, 验证通过 (客户端jwtVerson小于Redis中的版本号, 则说明过期了)

                if (long.Parse(jwtModel.jwtVersion) >= myJwtVersion)
                {
                    //表示校验通过,执行action中的业务
                    context.RouteData.Values.Add("auth", result);
                    await next();
                }
                else
                {
                    context.Result = new JsonResult(new { status = "error", msg = "jwt版本号错误" });
                    return;
                }

            }


        }
    }
View Code

 测试:

     访问CheckLogin3获取token, 然后携带token访问GetMsg3接口,此时请求成功; 然后去Redis中找到这个用户将jwtVersion增加1后,再次访问GetMsg3接口,则访问不通过,提示jwtVerson版本号错误

 剖析:

    既缓解了DB的压力,同时还能解决集群问题

 

四. 双token方案

  详见:

     https://www.cnblogs.com/yaopengfei/p/12449213.html

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-05-30 20:39  Yaopengfei  阅读(496)  评论(2编辑  收藏  举报