一、 在pom.xml中添加jwt依赖
<!--jwt依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<!-- 常用工具类依赖-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
二、 先定义一个全局错误捕获,下面要用来返回用户验证失败的信息
(一)、 自己在项目路创建一个放异常类的包,在包下创建两个类(名字随意)GlobalException、ServiceException。
@ControllerAdvice
public class GlobalException {
/**
* 全局异常
* 这里的 Result 是自己创建的返回参数的格式
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
public Result serviceException(ServiceException e){
return Result.fail(e.getCode(), e.getMessage());
}
}
@Getter
public class ServiceException extends RuntimeException {
/**
* 状态码
* 用lombok注解声明get方法,方便全局异常类获取自己返回的状态码
*/
private final Integer code;
public ServiceException(String msg) {
super(msg);
this.code = 500;
}
public ServiceException(Integer code, String msg) {
super(msg);
this.code = code;
}
}
三、定义一个过滤器用来匹配传入的Token
(一)、 自己在项目路创建一个包,在包下新建一个类然后继承HandlerInterceptor,重写preHandle方法
/**
* jwt 拦截过滤器
* 拦截规则
*/
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private UserMapper mapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头获取token
String token = request.getHeader("token");
// 如果token 为空、null、""这三种情况
if (StringUtils.isBlank(token)) {
// 前端可能 把token放请求参数里
token = request.getParameter("token");
}
// 如果请求头和请求参数都没有token就是没登录,直接抛出异常
if (StringUtils.isBlank(token)) {
throw new ServiceException(401, "请登录");
}
// 获取 token 中user的 id
String json;
User user;
try {
/**
* 通过JWT官方的解码方法把信息还原成自己定义的样子
* JWT.decode 解码
*/
json = JWT.decode(token).getAudience().get(0);
ObjectMapper objectMapper = new ObjectMapper();
Reader reader = new StringReader(json);
user = objectMapper.readValue(reader,User.class);
} catch (JWTDecodeException j) {
throw new ServiceException(401, "请登录");
}
// 根据解析出来的id去数据库查看有无该用户
User userDB = mapper.selectById(user.getUId());
if (user == null) {
throw new ServiceException(400, "未查询到该用户");
}
// 声明一个签证器
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("comXie")).build();
try {
// 验证token
jwtVerifier.verify(token);
} catch (JWTVerificationException jv) {
// 传过来的token与自己设置的验证器不匹配
throw new ServiceException(401, "请登录");
}
return true;
}
}
(二)、创建一个配置类使用上面的拦截器
/**
* jwt 配置类
*/
@Configuration
public class JwtConfig extends WebMvcConfigurationSupport {
/**
* 重写addInterceptors (拦截器)
* 把自定义的拦截器规则、拦截的路径配置完
* 在 JwtConfig 拦截 把请求拿去 JwtInterceptor 去校验
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor()) // 自定义的拦截规则 jwtInterceptor()
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login"); // 放行登录接口
super.addInterceptors(registry);
}
@Bean
public JwtInterceptor jwtInterceptor(){
return new JwtInterceptor();
}
}
四、自己在项目路创建一个包,在包下新建一个工具类TokenUtil(名字随意)
@Component // 交给ioc管理
public class TokenUtil {
// 查询数据库用
private static UserMapper staticMapper;
// 序列化/ 反序列化使用 使用的是jackson
private static ObjectMapper objectMapper;
@Resource
UserMapper mapper;
@PostConstruct
public void setStaticMapper(){
staticMapper = mapper;
}
/**
* 生成 jwt token 序列化用户实体,把用户实体当作载荷
* @param user
* @return
*/
public static String createToken(User user) throws JsonProcessingException {
objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user); // 转换成json字符串
return JWT.create().withAudience(json) // 将userid当作载荷
.withExpiresAt(DateUtil.offsetHour(new Date(),2)) //两小时后token过期
.sign(Algorithm.HMAC256("comXie")); // 以 comXie 作为密钥
}
/**
* 获取用户信息
* @return
*/
public static User getCurrentUser(){
try {
// 获取请求头的 token
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
if (StringUtils.isBlank(token)){ // token 为空抛出异常
throw new ServiceException(400,"参数有误token不能为空");
}
objectMapper = new ObjectMapper();
String json = JWT.decode(token).getAudience().get(0); //解析token获取载荷字符串
Reader reader = new StringReader(json);
User person = objectMapper.readValue(reader, User.class); //反序列化成对象
return person;
}catch (Exception e){
throw new ServiceException(e.toString());
}
}
}
五、使用工具类生成token,创建controller、service层在service中调用TokenUtil工具类生成token
(一)、controller
/**
* 登录
* @param account
* @param password
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
public Result userLogin(String account,String password){
String userToken = service.userLogin(account, password);
return Result.success(userToken,"成功");
}
(二)、service
@Override
public String userLogin(String account, String password) {
// 盐值
String MD5Yan = "comxie";
// 使用spring自带的工具类MD5加密密码
String mdPassword = DigestUtils.md5DigestAsHex((password + MD5Yan).getBytes());
// 查询条件
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUAccount, account)
.eq(User::getUPassword, mdPassword);
// 数据库查出的用户
User user = mapper.selectOne(lqw);
if (user==null){
throw new ServiceException(400,"账号密码错误");
}
// 清空密码
user.setUPassword(null);
String token = "";
// 生成token
try {
token = TokenUtil.createToken(user);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return token;
}
本文参考:B站up 程序员青戈 的【带小白做毕设】第12集