Json web token (JWT),用于进行身份验证,开销小,适用于单点登录。优点是token是以json加密的形式保存在客户端的,跨语言;能将用户需要的所有信息都加密到token里,不用多次查询数据库;不用在服务端保存会话信息,适用于分布式微服务。
jwt构成:
base64
加密后的header
和base64
加密后的payload
使用.
连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密Debugger工具:https://jwt.io/#debugger-io
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtToken {
boolean required() default true;
}
@Component
public class JwtUtil {
/**
* 静态方法调用非静态接口层(Service层)
*/
public static JwtUtil jwtUtil; //声明对象
@PostConstruct //初始化
public void init() {
jwtUtil = this;
jwtUtil.userService = this.userService;
}
@Autowired //注入
UserService userService;
/**
* 过期时间30分钟
*/
private static final long EXPIRE_TIME = 30 * 60 * 1000;//自定义修改
/**
* jwt 密钥
*/
private static final String SECRET = "abcde";//自定义修改
/**
* 生成签名,30分钟后过期
*
* @param userId
* @return
*/
public static String sign(String userId, String username) {
try {
//过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//私钥及加密算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);
//附带username和userID生成签名,可自定义附带信息,这里是用户ID和用户名称
return JWT.create()
// 将 user id 保存到 token 里面
.withClaim("userId", userId)
.withClaim("username", username)
// 分钟后token过期
.withExpiresAt(date)
// token 的密钥
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
/**
* 校验token
*
* @param token
* @return
*/
public static boolean checkSign(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.build();
// 验证token
DecodedJWT jwt = verifier.verify(token);
// String subject = jwt.getSubject();
// List audience = jwt.getAudience();
// 获取附带信息,并进行自定义验证,这里是验证根据userid查出来的用户名与token中附带的用户名是否一致
Map<String, Claim> claims = jwt.getClaims();
String userId = claims.get("userId").asString();
String username = claims.get("username").asString();
String un = jwtUtil.userService.getUsernameById(userId);
if (!un.equals(username)) {
throw new RuntimeException("token无效,请重新登录");
}
return true;
} catch (JWTVerificationException exception) {
throw new RuntimeException("无效token,请重新获取");
}
}
/**
* 获取token携带信息
* @param token
* @param name 附带信息名称
* @return
*/
public static String getTockenClaims(String token,String name){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier.verify(token);
Map<String, Claim> claims = jwt.getClaims();
return claims.get(name).asString();
}catch (JWTVerificationException exception) {
return "";
}
}
}
拦截带有@JwtToken
注解的请求,有的话进行验证
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("token");
// 如果不是映射到方法直接通过
if(!(o instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)o;
Method method=handlerMethod.getMethod();
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(JwtToken.class)) {
JwtToken jwtToken = method.getAnnotation(JwtToken.class);
if (jwtToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 验证 token
JwtUtil.checkSign(token);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* @Description: 添加拦截器
* @Param: [interceptorRegistry]
* @Return: void
*/
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
interceptorRegistry.addInterceptor(new JwtInterceptor())
// 拦截所有请求,通过判断是否有 @JwtToken 注解 决定是否需要登录
.addPathPatterns("/**");
}
}
登录时不使用注解并将生成的token返回给客户端,其他需要进行身份验证的请求时使用@JwtToken
,
@RequestMapping(value = "/login",method = RequestMethod.POST)
public JsonResult login(@RequestParam(value = "username") String username, @RequestParam("password") String password){
Map<String,Object> record=new HashMap<>();
record.put("username",username);
record.put("password",password);
Map data=userService.login(record);
if(data!=null){
String token= JwtUtil.sign(data.get("userId").toString(),username);
data.put("token",token);
data.remove("userId");
return JsonResult.build(1,"success",data);
}else {
return JsonResult.build(-1,"用户名或密码错误",null);
}
}
@JwtToken
@RequestMapping(value = "getList",method = RequestMethod.POST)
public JsonResult getList(@RequestParam("a") String a){
Map<String,Object> record=new HashMap<>();
record.put("a",a);
List list = userService.getList(record);
return JsonResult.ok(list);
}
@JwtToken
@PostMapping(("/test"))
public void test() {
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
String token = httpServletRequest.getHeader("token");
String username = JwtUtil.getTockenClaims(token,"username");
}
登录成功后得到token,可存放在sessionStorage、localStorage或cookie中
例如使用axios
/* http request
**请求拦截器
**在发送请求之前进行的一系列处理,根据项目要求自行配置
**例如:loading
*/
axios.interceptors.request.use(
(config) => {
// 请求响应时间
config.timeout = 60 * 1000
// config.data = JSON.stringify(config.data)
config.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
token: sessionStorage.getItem('accessToken')//获取token
// 'Content-Type': 'application/json'
}
return config
},
function(error) {
// 对请求错误做处理
return Promise.reject(error)
}
)