前言
JWT,全称JSON? Web Token,将用户信息加密到token中,服务器不保存任何用户信息,服务器通过使用保存的密匙验证token的正确性,只要正确就通过验证。
JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户
组成
一个token三部分组成,三部分用“.”分割,按顺序分为
1、头部(header)
- 声明类型是jwt
- 声明加密算法? 通常使用HMAC SHA256
2、载荷(payload)
3、签证(signature)
使用
1、导入pom依赖
<!-- jwt-->
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2、在utils包下新建TokenUtil类,生成token
使用userId作为载荷,以password作为秘钥
package com.aqya.utils;
import cn.hutool.core.date.DateUtil;
import com.aqya.service.IUserService;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Date;
/**
* @author 阿青呀
* @date 2022/05/11 14:20
**/
public class TokenUtils {
/**
* 生成 Token
* @param userId
* @param sign
* @return
*/
public static String getToken(String userId,String sign){
return JWT.create().withAudience(userId) // 将user id 保存到token 作为载荷
.withExpiresAt(DateUtil.offsetHour(new Date(),2)) //2小时过期
.sign(Algorithm.HMAC256(sign)); //以password 作为 token密匙
}
}
3、在userService里面设置token
在userDto加入字符型token字段,登录业务不需要原来所有的实体类数据,就自己再创建专门登入使用的类userDto,一般就是用户名密码之类必须的,然后将实体类的用户名密码拷贝到这个userDto之中,即下面的语句
BeanUtil.copyProperties(userDto,one,true);
?userService如下
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.log.Log;
import com.aqya.common.Constants;
import com.aqya.controller.dto.UserDto;
import com.aqya.entity.User;
import com.aqya.exception.ServiceException;
import com.aqya.mapper.UserMapper;
import com.aqya.service.IUserService;
import com.aqya.utils.TokenUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author aqya
* @since 2022-05-03
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
private static final Log LOG = Log.get();
@Override
public UserDto login(UserDto userDto) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",userDto.getUsername());
queryWrapper.eq("password",userDto.getPassword());
User one;
//查出多条语句
// List<User> list = list(queryWrapper);
// return list.size() != 0;
//查到多个用户名密码一致,抓取异常
try{
one = getOne(queryWrapper); //从数据库查询信息
}catch (Exception e){
LOG.error(e);
throw new ServiceException(Constants.CODE_500,"系统错误");
}
return one;
}
//业务异常
if(one !=null){
BeanUtil.copyProperties(one,userDto,true);
//设置token
String token = TokenUtils.getToken(one.getId().toString(), one.getPassword());
userDto.setToken(token);
return userDto;
}else {
throw new ServiceException(Constants.CODE_600,"用户名或者密码错误");
}
}
}
4、在axous的request.js放开
let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
if(user){ //user存在 设置token
config.headers['token'] = user.token; // 设置请求头
}
完整的request.js
import axios from 'axios'
import ElementUI from 'element-ui'
const request = axios.create({
baseURL: 'http://localhost:8081', // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
timeout: 5000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
if(user){ //user存在 设置token
config.headers['token'] = user.token; // 设置请求头
}
return config
}, error => {
return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
//当权限验证不通过时候,给出提示
if (res.code === '401'){
ElementUI.Message({
message: "请先登录",
type: 'error'
})
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export default request
这时候登录过后在浏览器的请求标头一个要有token
5、拦截器验证token信息
在Interceptor包下建立JWTInterceptor
JWTInterceptor 实现 HandlerInterceptor接口,重写preHandle方法
package com.aqya.config.interceptor;
import cn.hutool.core.util.StrUtil;
import com.aqya.common.Constants;
import com.aqya.entity.User;
import com.aqya.exception.ServiceException;
import com.aqya.service.IUserService;
import com.aqya.service.impl.UserServiceImpl;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 阿青呀
* @date 2022/05/11 14:57
**/
@Component
public class JWTInterceptor implements HandlerInterceptor {
@Autowired
private IUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("token");
//如果不是映射到方法,直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
//执行认证
if(StrUtil.isBlank(token)){
throw new ServiceException(Constants.CODE_401,"无token,请重新登录");
}
//获取token 的user id
String userId;
try{
userId = JWT.decode(token).getAudience().get(0);
}catch (JWTDecodeException j){
throw new ServiceException(Constants.CODE_401,"token验证失败");
}
//更加token中的userId查询数据库
User user = userService.getById(userId);
if(user == null){
throw new ServiceException(Constants.CODE_401,"用户不存在,请重新登录");
}
//用户密码加签验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try{
jwtVerifier.verify(token); //验证token
}catch (JWTVerificationException j){
throw new ServiceException(Constants.CODE_401,"token验证失败,请重新登录");
}
return true;
}
}
6、注册拦截器重写addInterceptors方法
package com.aqya.config;
import com.aqya.config.interceptor.JWTInterceptor;
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.WebMvcConfigurer;
/**
* @author 阿青呀
* @date 2022/05/11 15:13
**/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**") //拦截所有请求,通过验证token是否合法决定是否登录
.excludePathPatterns("/user/login","/user/register","/**/export","/**/import"); //排除登录注册导入导出
}
//注入到bean 不然可能拿不到userService
@Bean
public JWTInterceptor jwtInterceptor(){
return new JWTInterceptor();
}
}
里面有些是我自己定义的类,如userDto,userServiceImpl,异常处理类ServiceException,也有使用到hutool,需要导入依赖。
|