springBoot + jwt
1.springboot项目中在pom.xml引入以下jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.3.0</version>
</dependency>
2.配置类InterceptorConfig 类继承 WebMvcConfigurationSupport
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
//1、继承WebMvcConfigurationSupport
@Configuration//表示这是一个配置类
public class InterceptorConfig extends WebMvcConfigurationSupport {
//2、addInterceptors方法是继承WebMvcConfigurationSupport需要重写一个拦截器的规则jwtInterceptor()他通过@Bean的方法将对象注入到spring容器里面
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())// 配置jwt的拦截器规则
.addPathPatterns("/**") //addPathPatterns("/**")/**表示拦截所有的请求路径
.excludePathPatterns("/erpUserInfo/login"); //应该拦截除了登录,注册请求以外的所有路径
super.addInterceptors(registry);
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
3.创建JwtInterceptor 继承 HandlerInterceptor
前端存登录生成的token,访问接口时获取前端请求头携带的token,若token匹配则验证通过,否则抛出异常
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private ErpUserInfoMapper userMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServiceException {
//1、首先用request拿到header(请求头)里的token参数
String token = request.getHeader("Authorization");
//2、拿到token参数以后进行校验,看他有没有.getparameter获取到的是url参数比如:xxx?token=... 可以用1和2两种方式接受参数一个是头一个是url参数,两个里面有一个获取到了token参数就认为他是有这个参数的
if (StrUtil.isBlank(token)) {
token = request.getParameter("token");
}
// 如果不是映射到方法直接通过 配合自定义注解使用
//if (handler instanceof HandlerMethod) {
//AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);
//if (annotation != null) {
//return true;
//}
//}
//3、如果请求头和参数里面都没有则抛出异常返回信息“请登录” 执行认证
if (StrUtil.isBlank(token)) {
throw new ServiceException("401", "请登录");
}
//4、如果有token的话 token里面存储的是字符串所以定义的时候是string getAudience()可以存储信息 获取 token 中的 userid JWT.decode(token)解码解码完成之后拿到getAudience()里面的第一个数据get(0)
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new ServiceException("401", "请登录");
}
//5、 根据token中的userid查询数据库进行信息校验Integer.valueOf(userId)把字符串转化为数字然后查询数据库
ErpUserInfo erpUserInfo = userMapper.selectById(Integer.valueOf(userId));
if (erpUserInfo == null) {
throw new ServiceException("401", "请登录");
}
//6、 用户密码加签验证:通过user拿到密码 jwtVerifier是一个验证器,通过用户密码加密之后生成的一个验证器 通过验证器的verify方法继续验证token,如果验证通过return true,如果验证失败则跑一个异常
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(erpUserInfo.getPassword())).build();
try {
jwtVerifier.verify(token); // 验证token
} catch (JWTVerificationException e) {
throw new ServiceException("401", "请登录");
}
String res = stringRedisTemplate.opsForValue().get(token);
if (res == null){
throw new ServiceException("401", "请登录");
}
return true;
}
}
4.创建TokenUtils工具类 生成token与获取token对应的用户
package com.lx.erp.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Date;
@Component
public class TokenUtils {
//1、定义一个静态mapper的原因,因为下面有static方法,在static方法里面像使用mapper那么mapper必须是static修饰的(静态的方法只能访问静态的变量)
private static ErpUserInfoMapper staticUserMapper;
@Resource
ErpUserInfoMapper userMapper;
@PostConstruct
public void setUserService() {
staticUserMapper = userMapper;
}
/**
* 2、生成token
*
* @return
*/
public static String genToken(String userId, String sign) {
return JWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期 withExpiresAt设置过期时间。offsetHour(new Date(), 2)表示当前日期new Date()往后偏移两个小时,他返回的是一个时间
.sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥.sign(Algorithm.HMAC256(sign))进行加密 sign是传过来的参数,传什么参数生成什么类型的验证器
}
/**
* 3、获取当前登录的用户信息
*只要当前请求有token我就可以通过token去拿到当前请求的用户的所有的信息(是从数据库里面查出来的 )
* @return user对象
*/
public static ErpUserInfo getCurrentUser() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("Authorization");
if (StrUtil.isNotBlank(token)) {
String userId = JWT.decode(token).getAudience().get(0);
return staticUserMapper.selectById(Integer.valueOf(userId));
}
} catch (Exception e) {
return null;
}
return null;
}
}
5.token的使用
String token = TokenUtils.genToken(erpUserInfo1.getId().toString(),erpUserInfo1.getPassword());
6.自定义注释
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}
7.前端登录接口存储token
login(this.formInline).then((res) => {
console.log(res)
if (res.status === 200) {
window.localStorage.setItem('token', res.data, '7d')
}
})
8.前端请求拦截器
//请求拦截器
instance.interceptors.request.use((config) => {
showLoading()
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
const token = window.localStorage.getItem('token');
if (token) { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
config.headers.Authorization = token;
} else {
router.push("/");
if (config.url !== '/api/erpUserInfo/login') {
alert("登录已过期,请重新登录")
}
}
//若请求方式为post,则将data参数转为JSON字符串
if (config.method === 'POST') {
config.data = JSON.stringify(config.data);
}
return config;
}, (error) =>
// 对请求错误做些什么
Promise.reject(error));