前言
核心:
根据用户生成token,返回前端,对于后续每一次操作都携带token,如果token存在就进行刷新,我设置的token时间为30分钟,如果用户30分钟内没有任何操作,就让其重新登录。
实现逻辑:
1.正常:前端传入token --> 后端拦截获取token --> redis验证token --> 获取到值 --> 更新时长并放行
2.异常:redis验证token --> 获取不到值 --> jwt验证token是否正确 --> 正确则返回用户长时间未操作,不正确则返回非法token
正文
1.所需依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.37</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.配置信息
#mysql数据库配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/crm?characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 #Redis数据库配置 spring.redis.port=6379 spring.redis.host=192.168.57.128 spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.max-idle=8 spring.redis.jedis.pool.min-idle=0 spring.redis.jedis.pool.max-wait=-1 #token过期时间 token.expires=3600
3.用户登录实现
具体逻辑
1.根据前端传输的用户信息进行校验,判断用户是否存在,密码是否正确
校验密码这块请前往spring对密码的处理拷贝即可
2.设置基础信息AuthInfo,这个类交给spring管理,方便后续直接取用
3.JwtUtil生成token, 存入redis中,代码中的Constants.TOKEN_PREFIX为sys:user:token:
4.返回token给前端
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {//这里引入依赖,配置了基本的信息就可以直接注入使用
@Autowired private RedisTemplate<Object, Object> redisTemplate;@Value("${token.expires}") private long expires;@Override public LoginVO login(LoginDTO loginDTO) {//判断用户是否存在 User user = getOne(User.gw().eq(User::getUserName, loginDTO.getUsername())); if (Objects.isNull(user)) { //这可以写注册的逻辑 throw new ServiceException("用户不存在"); } //校验密码 boolean b = PasswordEncode.matches(loginDTO.getPassword(), user.getUserPassword()); if (!b) { throw new ServiceException("密码错误"); }/** * 设置基础信息,方便后续使用 */ AuthInfo authInfo = SpringUtil.getBean(AuthInfo.class); authInfo.setDeptId(user.getDeptId()); authInfo.setRoleId(user.getRoleId()); authInfo.setUserId(user.getUserId()); authInfo.setUsername(user.getUserName());//生成token String token = JwtUtil.getToken(user); //存入redis try { ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set(Constants.TOKEN_PREFIX + token, authInfo, expires, TimeUnit.MINUTES); } catch (Exception e) { throw new RedisException("redis缓存Token失败"); }return LoginVO.builder().token(token).build();}
}
实体类
实体类根据自己项目所需进行编写,把上面代码换成自己的即可
工具类
JwtUtil
@Component public class JwtUtil {/** * 生成token * * @param user * @return */ public static String getToken(User user) { return JWT.create() .withClaim("username", user.getUserName()) .withExpiresAt(DateUtil.offsetMinute(new Date(), 30)) .sign(Algorithm.HMAC256(user.getUserPassword())); }/** * 刷新token就是重新设置时间 * * @param token * @param authInfo * @return */ public static boolean refreshToken(String token, AuthInfo authInfo) { try { RedisTemplate<Object, Object> redisTemplate = SpringUtil.getBean("redisTemplate"); ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set(Constants.TOKEN_PREFIX + token, authInfo, JwtUtil.exp, TimeUnit.MINUTES); return true; } catch (Exception e) { return false; } } public static boolean verify(String token) { AuthInfo authInfo = SpringUtil.getBean(AuthInfo.class); User user = uService.getById(authInfo.getUserId()); // 验证 token JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getUserPassword())).build(); return Objects.nonNull(jwtVerifier.verify(token)); }}
4.拦截验证
具体逻辑
1.验证请求是否携带token
2.根据token去redis中取出AuthInfo,代码中的Constants.TOKEN_PREFIX为sys:user:token:
1.如果取不到,就使用jwt验证token。
2.如果取到,就重新设置token时长,保存到redis中。
public class TokenVerify implements HandlerInterceptor {@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //校验请求头token String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) { throw new TokenException("未携带Token"); } /** * 校验token */ //先从redis去验证 RedisTemplate<Object, Object> redisTemplate = SpringUtil.getBean("redisTemplate"); AuthInfo authInfo = (AuthInfo)redisTemplate.opsForValue().get(Constants.TOKEN_PREFIX + token); if (Objects.isNull(authInfo)) { //验证是否合法 try { JwtUtil.verify(token); } catch (TokenExpiredException e) { //这里我们不处理,刷新token只针对redis中的token }catch (JWTException e) { throw new JWTException("非法token"); } throw new TokenException("token已过期,请重新登录"); } //服务器宕机导致用户数据丢失 if (Objects.isNull(SpringUtil.getBean(AuthInfo.class).getUserId())) { throw new RuntimeException("服务器异常,请重新登录"); } //刷新token时长 if (!JwtUtil.refreshToken(Constants.TOKEN_PREFIX + token, authInfo)) { throw new TokenException("刷新token失败"); } return true; } }
5. 注册拦截器
具体逻辑
1.注册拦截器,并且对登录和验证码接口放行
@Configuration public class InterceptorConfig implements WebMvcConfigurer {@Override public void addInterceptors(InterceptorRegistry registry) { //验证token registry.addInterceptor(new TokenVerify()) .addPathPatterns("/api/**") .excludePathPatterns( Urls.User.LOGIN, Urls.User.CAPTCHA); } }