最近项目上碰到一个问题,使用SpringBoot拦截器实现 JWT登录鉴权时,无法Pass swagger2的请求,经过研究最终解决了该问题,方案如下,如有更好的建议,请大家不吝赐教。
目的:
使用JWT进行登录,使用前后端分离的token鉴权。
但是swagger2的文档在开发阶段需要放行,不经过权限认证。
切面鉴权失败后抛出异常,使用SpringBoot的全局异常处理器统一处理,返回到客户端。
如果碰到问题,请私信我。
TokenUtil实现JWT封装工具
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
public class TokenUtil {
/**
* 过期时间,120分钟
*/
private static final long EXPIRE_TIME = 120 * 60 * 1000;
private static final String TOKEN_SECRET = "@#$%#$%"; //密钥
public static final String TOKEN = "token";
/**
* 签名生成
* @param userName 用户名
* @return token
*/
public static String sign(String userName) {
return JWT.create()
.withSubject(userName)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE_TIME))
// 使用了HMAC256加密算法。
.sign(Algorithm.HMAC256(TOKEN_SECRET));
}
/**
* 签名验证
*
* @param token token
* @return userName
*/
public static String verify(String token) {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET))
.build();
DecodedJWT jwt = verifier.verify(token);
if (jwt != null) {
return jwt.getSubject();
} else {
return null;
}
} catch (TokenExpiredException e) {
throw new UnTokenException("Token expired.");
} catch (Exception e) {
throw new UnTokenException("Token error.");
}
}
/**
* 检查 token 是否需要更新
*
* @param token token 值
* @return 过期时间
*/
public static boolean isNeedUpdate(String token) {
// 获取 token 过期时间
try {
Date expiresAt = JWT.require(Algorithm.HMAC256(TOKEN_SECRET))
.build()
.verify(token)
.getExpiresAt();
// 时间过去一半之后更新token
return (expiresAt.getTime() - System.currentTimeMillis()) < (EXPIRE_TIME >> 1);
} catch (TokenExpiredException e) {
return true;
} catch (Exception e) {
return false;
}
}
}
TokenAuthenticationAspect只控制 SpringBoot的Controller
/**
* Token鉴权切面
*/
@Slf4j
@Aspect
@Component
public class TokenAuthenticationAspect {
/**
* 登录路径 ->放行
*/
private static final String LOGIN = "/user/login";
/**
* 声明切入点表达式。
*/
@Pointcut("execution(public * com.kczl.swarm.controller..*..*(..))")
public void pointcut() {
}
/**
* 环绕通知
* 可以理解为对这个方法进行环绕通知
* ProceedingJoinPoint 参数 用于环绕通知,
* 使用proceed()方法来执行目标方法,可以理解为 前置通知结束 开始执行使用该注解的方法。
*/
@Around("pointcut()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader(TokenUtil.TOKEN);
if (request.getRequestURI().equals(LOGIN)) {
return joinPoint.proceed();
}
if (StringUtils.isNotBlank(token)) {
String sub = TokenUtil.verify(token);
if (StringUtils.isBlank(sub)) {
throw new UnTokenException("Token is invalidate.");
}
if (TokenUtil.isNeedUpdate(token)) {
String newToken = TokenUtil.sign(sub);
attributes.getResponse().setHeader(TokenUtil.TOKEN, newToken);
}
} else {
throw new UnTokenException("Not login.");
}
//执行业务逻辑,放行
return joinPoint.proceed();
}