04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期

本文主要是介绍04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

1. 登录后获取用户信息

非常好实现. 只要新建一个controller, 并调用SS提供的Authentication对象即可

package com.sunsplanter.controller;@RestController
public class UserController {@GetMapping(value = "api/login/info")public R loginInfo(Authentication authentication) {TUser tUser = (TUser)authentication.getPrincipal();return R.OK(tUser);}}

未登录状态下可以直接访问 api/login/info吗?

不可以. 因为在安全配置类已经写明了, 仅登陆界面允许任何人访问, 其他所有界面都需要认证

<由于未写JWT, 默认使用Session 保存会话,> ???好像不对
因此只要我们先通过登录接口登录, 然后再直接访问获取用户信息接口即可
在这里插入图片描述

2. 使用JWT打通登录后各个页面的认证

前后端分离的项目一般会使用token(jwt)实现登录状态的保持;(java web : session)

token其实就是一个随机字符串(字符串要求是唯一的,不同人的token都不能相同),当用户在登录页面输入账号和密码后,前端将账号密码发送给后端,后端检验完账号和密码后,会生成一个随机不重复的字符串即(token),并将其响应给前端,前端拿到token后,需要在客户端进行持久化存储(一般会写在localStorage或者sessionStorage中),那么下次在向后端数据接口发送请求的时候,一般需要将token一并发送给后端数据接口,后端数据接口会对token进行校验,如果合法则正常响应请求,如果不合法,则提示未登录。

2.1 sessionStorage与localStorage

它们是javascript对象,浏览器支持这两个对象,可以直接使用. 属于前端范畴
localStorage和sessionStorage都是用来在浏览器客户端存储临时信息的对象;

sessionStorage、localStorage区别?

sessionStorage只在一个浏览器页面有效,比如你打开一个新的tab浏览器页会失效,你关闭浏览器后,再打开浏览器也会失效;
localStorage在整个浏览器中都有效,重启浏览器也有效;除非你手动删除了localStorage,才会失效;

2.2 修改登录成功拦截器

根据流程图, 在查询到对象并返回给SS后, SS会调用成功拦截器,
就在这个拦截器中, 根据查询到的对象生成JWT并同时存进Redis和返回前端.

//登录成功会自动执行这个类中的onAuthenticationSuccess方法, 该方法返回自定义的Json给前端
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Resourceprivate RedisService redisService;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//登录成功,执行该方法,在该方法中返回json给前端,就行了TUser tUser = (TUser) authentication.getPrincipal();//1.生成JWT//由于createJWT方法定义的参数是序列化后的对象, 因此先调用JSONUtils序列化对象String userJSON = JSONUtils.toJSON(tUser);String jwt = JWTUtils.createJWT(userJSON);//2.写入RedisredisService.setValue(Constants.REDIS_JWT_KEY + tUser.getId(), jwt);//3. 设置JWT的过期时间(如果选择记住我, 过期时间是7天, 否则30分钟)String rememberMe = request.getParameter("rememberMe");//勾选了记住我就设置为7天, 其中EXPIRE_TIME就是7天,DEFAULT_EXPIRE_TIME是半小时if (Boolean.parseBoolean(rememberMe)){redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId() , Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}//登录成功的统一结果R result = R.OK(jwt);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);}
}

常量类为

package com.sunsplanter.constant;/*** 常量类*/
public class Constants {public static final String LOGIN_URI = "/api/login";//redis的key的命名规范: 项目名:模块名:功能名:唯一业务参数(比如用户id)public static final String REDIS_JWT_KEY = "dlyk:user:login:";//redis中负责人的keypublic static final String REDIS_OWNER_KEY = "dlyk:user:owner";//jwt过期时间7天public static final Long EXPIRE_TIME = 7 * 24 * 60 * 60L;//jwt过期时间30分钟public static final Long DEFAULT_EXPIRE_TIME = 30 * 60L;
}

redis类 为

package com.sunsplanter.servicepublic interface RedisService {void setValue(String key, Object value);Object getValue(String key);Boolean removeValue(String key);//给jwt设置过期时间Boolean expire(String key, Long timeOut, TimeUnit timeUnit);
}
package com.sunsplanter.service.impl;@Service
public class RedisServiceImpl implements RedisService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void setValue(String key, Object value) {redisTemplate.opsForValue().set(key, value);}@Overridepublic Object getValue(String key) {return redisTemplate.opsForValue().get(key);}@Overridepublic Boolean removeValue(String key) {return redisTemplate.delete(key);}//给JWT设置过期时间@Overridepublic Boolean expire(String key, Long timeOut, TimeUnit timeUnit) {return redisTemplate.expire(key, timeOut, timeUnit);}
}

在网页登录后可以看到已经存到了localstorage
在这里插入图片描述

3 Filter验证Token

如流程图所示, 第一次登录成功过后, 往后每次登录会携带token登录,

因此, 在config.filter文件夹下新建一个token过滤器类, 该类实现OncePerRequestFilter
并在SS的安全配置类中中添加进去

package com.sunsplanter.config.filter;@Component
public class TokenVerifyFilter extends OncePerRequestFilter{@Resourceprivate RedisService redisService;//spring boot框架的ioc容器中已经创建好了该线程池,可以注入直接使用@Resourceprivate ThreadPoolTaskExecutor threadPoolTaskExecutor;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if (request.getRequestURI().equals(Constants.LOGIN_URI)) { //如果是登录请求,此时还没有生成jwt,那不需要对登录请求进行jwt验证//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个FilterfilterChain.doFilter(request, response);} else {String token = null;if (request.getRequestURI().equals(Constants.EXPORT_EXCEL_URI)) {//从请求路径的参数中获取tokentoken = request.getParameter("Authorization");} else {//其他请求都是从请求头中获取tokentoken = request.getHeader("Authorization");}if (!StringUtils.hasText(token)) {//token验证未通过的统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//验证token有没有被篡改过if (!JWTUtils.verifyJWT(token)) {//token验证未通过统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}TUser tUser = JWTUtils.parseUserFromJWT(token);String redisToken = (String) redisService.getValue(Constants.REDIS_JWT_KEY + tUser.getId());//验证token非空if (!StringUtils.hasText(redisToken)) {//token验证未通过统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_EXPIRED);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//验证token是否与redis中的一致if (!token.equals(redisToken)) {//token验证未通过的统一结果类R result = R.FAIL(CodeEnum.TOKEN_IS_NONE_MATCH);//把R对象转成jsonString resultJSON = JSONUtils.toJSON(result);//把R以json返回给前端ResponseUtils.write(response, resultJSON);return;}//jwt验证通过了,那么在spring security的上下文环境中要设置一下,设置当前这个人是登录过的,你后续不要再拦截他了UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser, tUser.getLoginPwd(), tUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//刷新一下token(异步处理,new一个线程去执行)/*new Thread(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}}).start();*///异步处理(更好的方式,使用线程池去执行)threadPoolTaskExecutor.execute(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}});//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个FilterfilterChain.doFilter(request, response);}}
}

4. token续期

现在一个问题是

token一旦发布, 是7天/30分钟 .

原来没有续期时 , token只能获得与提前删除.

然而一个现实需求是

假如用户获得是30分钟的token. 在这三十分钟内, 只要用户有任何操作, 我们应当自动刷新token到30分钟 , 否则假如不管用户怎么操作, token都固定30分钟刷新 , 这显然不符合逻辑

目标为, 任何除登出的用户登录都会重置token过期时间

然后是代码放在哪里的问题

我们知道, 即使登录过后 , 我们在每一次操作时仍会用TokenVerifyFilter检查token的有效期.

因此直接把续期代码放到该类中即可, 每次有操作->执行TokenVerifyFilter检查token有效期->有效则续期token

在TokenVerifyFilter中新增刷新代码

//刷新一下token(异步处理,new一个线程去执行)
//没必要因为续期token而阻塞整个进程, 毕竟此时已经校验过了不影响本次执行new Thread(() -> {//刷新tokenString rememberMe = request.getHeader("rememberMe");if (Boolean.parseBoolean(rememberMe)) {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.EXPIRE_TIME, TimeUnit.SECONDS);} else {redisService.expire(Constants.REDIS_JWT_KEY + tUser.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);}}).start();

这篇关于04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/733089

相关文章

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

TP-Link PDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务

《TP-LinkPDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务》近期,路由器制造巨头普联(TP-Link)在用户群体中引发了一系列重要变动,上个月,公司发出了一则通知,明确要求所... 路由器厂商普联(TP-Link)上个月发布公告要求所有用户必须完成实名认证后才能继续使用普联提供的 D

如何利用Java获取当天的开始和结束时间

《如何利用Java获取当天的开始和结束时间》:本文主要介绍如何使用Java8的LocalDate和LocalDateTime类获取指定日期的开始和结束时间,展示了如何通过这些类进行日期和时间的处... 目录前言1. Java日期时间API概述2. 获取当天的开始和结束时间代码解析运行结果3. 总结前言在J

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

java获取图片的大小、宽度、高度方式

《java获取图片的大小、宽度、高度方式》文章介绍了如何将File对象转换为MultipartFile对象的过程,并分享了个人经验,希望能为读者提供参考... 目China编程录Java获取图片的大小、宽度、高度File对象(该对象里面是图片)MultipartFile对象(该对象里面是图片)总结java获取图片

Java通过反射获取方法参数名的方式小结

《Java通过反射获取方法参数名的方式小结》这篇文章主要为大家详细介绍了Java如何通过反射获取方法参数名的方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、解决方式方式2.1: 添加编译参数配置 -parameters方式2.2: 使用Spring的内部工具类 -

Java如何获取视频文件的视频时长

《Java如何获取视频文件的视频时长》文章介绍了如何使用Java获取视频文件的视频时长,包括导入maven依赖和代码案例,同时,也讨论了在运行过程中遇到的SLF4J加载问题,并给出了解决方案... 目录Java获取视频文件的视频时长1、导入maven依赖2、代码案例3、SLF4J: Failed to lo

Redis存储的列表分页和检索的实现方法

《Redis存储的列表分页和检索的实现方法》在Redis中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,... 目录一、Redis 列表的基本操作二、分页实现三、检索实现3.1 方法 1:客户端过滤3.2 方法