1. application配置属性
#安全验证jwt
jwt.header=Authorization
#令牌前缀,前后端发送token添加上此令牌
jwt.token-start-with=Bearer
#令牌签名
jwt.sing=!@#$/*-QRIU234^&^Y*
2. 获取属性的实体对象
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//从配置文件中获取jwt关键字段
@Data
@Component
public class JWTProperty {
@Value("${jwt.header}")
private String tokenHeader;
@Value("${jwt.token-start-with}")
private String tokenStartWith;
@Value("${jwt.sing}")
private String sing ;
}
3. jwt工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.march.system.utils.property.JWTProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Calendar;
import java.util.Map;
@Slf4j
@Component
public class JWTUtils {
//获取token时,获取jwt配置信息,
@Autowired
private JWTProperty jwtProperty ;
@Value("${jwt.sing}")
private String sing ;
private static ThreadLocal<Object> requestUserThreadLocal = new ThreadLocal<>();
//验证合法性时,由调用者(拦截器)传递
public JWTUtils(JWTProperty jwtProperty){
this.jwtProperty = jwtProperty;
}
public JWTUtils () {}
//生成token
public String getToken(Map<String, Object> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); //七天过期
JWTCreator.Builder builder = JWT.create();
map.forEach((K, V) -> {
builder.withClaim(K, V.toString());
});
return builder
.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(jwtProperty.getSing()));
}
// 验证token合法性
public DecodedJWT verify(String token) {
this.sing = jwtProperty.getSing();
return JWT.require(Algorithm.HMAC256(jwtProperty.getSing())).build().verify(token);
}
//将token存储在当前线程
public void setThreadLocalToken(String token) {
requestUserThreadLocal.set(token);
}
//从当前线程中获取token
public String getThreadLocalToken() {
return requestUserThreadLocal.get().toString();
}
//获取DecodedJWT(解码后的token)
public DecodedJWT getThreadLocalDecodedJWT() {
String token = getThreadLocalToken();
return JWT.require(Algorithm.HMAC256(jwtProperty.getSing())).build().verify(token);
}
//清空
public void removeThreadLocal() {
requestUserThreadLocal.remove();
}
//获取当前登录对象id
public Long getThreadLocalUserId(){
DecodedJWT verify = getThreadLocalDecodedJWT();
Integer type = getThreadLocalType();
Long userId = null;
userId = Long.parseLong(verify.getClaim("userId").asString());
return userId;
}
}
4. 登录接口,调用生成token
private final JWTUtils jwtUtils;
……
Map<String,Object> map = new HashMap<>();
// token存储当前用户id
map.put("bloggerId",blogger.getBloggerId());
String token = jwtUtils.getToken(map);
//将token存储在当前线程中
jwtUtils.removeThreadLocal();
jwtUtils.setThreadLocalToken(token);
//封装token "Authorization": "Bearer YWxhZGRpbjpvcGVuc2VzYW1l"
Map<String ,String> tokenMap = new HashMap<>();
tokenMap.put(jwtProperty.getTokenHeader(),jwtProperty.getTokenStartWith()+" "+token);
5. 控制是否需要登录验证的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//使用该注解表示类或方法必须进行token验证
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifyToken {
}
对于某一个接口,如果需要进行登录验证,那么就对其使用这个注解
例如:
@DeleteMapping
@VerifyToken
public Result deleteUser(Long id) {
……
}
6. jwt验证拦截器
配置一个继承HandlerInterceptor
的拦截器,用来验证请求是否带有token,且token是否有效。
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.exceptions.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.march.system.utils.annution.VerifyToken;
import com.march.system.utils.property.JWTProperty;
import com.march.system.utils.JWTUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class JWTInterceptor implements HandlerInterceptor {
private JWTProperty jwtProperty ; //实体类,用来获取配置文件application中关于jwt的配置,其中使用 @Value("${jwt.header}") 来获取值
//jwt工具类,见上面
private JWTUtils jwtUtils ;
//这里是没有Bean引入的,在别的地方通过对象引入。注意new的对象是一个全新的、独立的对象
public JWTInterceptor(JWTProperty jwtProperty,JWTUtils jwtUtils){
this.jwtProperty = jwtProperty;
this.jwtUtils = jwtUtils;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map = new HashMap<>();
response.setContentType("application/json;charset=UTF-8");
String json =null;
//获取请求头中令牌
Enumeration<String> headerNames = request.getHeaderNames();
String token = request.getHeader("authorization");
//如果不是映射到Controller直接放行
if(!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
//自定义注解,判断当前请求是否需要验证
// 如果当前请求方法添加@VerifyToken注解
if(method.isAnnotationPresent(VerifyToken.class)){
//判断token不为空,长度不为0,非空白符,且包含字符串startWith
if(StrUtil.isNotEmpty(token) && token.startsWith(jwtProperty.getTokenStartWith())) {
//判断token是否只有前缀没有身体
if(token.length() <= (jwtProperty.getTokenStartWith().length()+1)) {
return false;
}
//切除token前缀
token = token.substring(jwtProperty.getTokenStartWith().length()+1);
log.info("【切除后token="+token+"】");
if(StrUtil.isNotEmpty(token)){
try{
//检验token是否合法
jwtUtils.verify(token);
jwtUtils.setThreadLocalToken(token);
return true;
}catch (SignatureVerificationException | InvalidClaimException | AlgorithmMismatchException | TokenExpiredException e) {
e.printStackTrace();
map.put("message","您还没有登录,请先登录");
}
map.put("code",401);
json = new ObjectMapper().writeValueAsString(map);
response.getWriter().println(json);
return false;
}
}
map.put("message","您还没有登录,请先登录");
map.put("code",401);
json = new ObjectMapper().writeValueAsString(map);
response.getWriter().println(json);
return false;
}
return true;
}
}
7. 拦截器需要注册在MVC中,并指明需要拦截的目标
import com.march.system.utils.JWTUtils;
import com.march.system.utils.property.JWTProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//从配置文件中获取JWT全局使用重点词
@Autowired
private JWTProperty jwtProperty;
//关于jwt的工具类
@Autowired
private JWTUtils jwtUtils ;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor(jwtProperty,jwtUtils))
//非拦截接口:登录,注册,注册时发送邮箱
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/verify")
.excludePathPatterns("/api/**/register")
.excludePathPatterns("/api/*/login");
}