说明
背景:项目采用springcloud框架,用户鉴权从网关走的,但是当单独部署springboot服务时,没有鉴权第三方扫描通不过。
方案:在springboot微服务中单独集成jwt鉴权。
一、引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
二、自定义拦截器
package com.gstanzer.supervise.jwt;
import com.alibaba.fastjson.JSONObject;
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.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.gstanzer.supervise.common.CommonData;
import com.gstanzer.supervise.exception.TokenException;
import com.gstanzer.supervise.redis.CtgRedisUtil;
import com.gstanzer.supervise.utills.RedisUtils;
import com.gstanzer.supervise.utils.TokenUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class AuthenticationInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(AuthenticationInterceptor.class);
@Resource
private CtgRedisUtil redisUtils;
// @Resource
// private RedisUtils redisUtils;
@Value(("${token.expire.time}"))
private Integer expireTime;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("gst-token");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
logger.info("=====pass token 跳过token 拦截=====");
return true;
}
}
// 执行认证
if (token == null) {
logger.info("token:" + token);
logger.info("请求路径:" + httpServletRequest.getRequestURI());
throw new TokenException("无token,请重新登录","tokenError");
}
// 解析token信息
String userId = "";
Map<String, String> userMap = TokenUtils.getUserInfoByToken(token);
if (userMap != null) {
userId = userMap.get("userId");
logger.info("解析token获得userId为:{}", userId);
}
if(redisUtils.exists("token_"+userId)){
//更新token有效时间
redisUtils.set("token_"+userId, token, expireTime);
// redisUtils.set("token_"+userId, token, Long.valueOf(String.valueOf(expireTime)), TimeUnit.SECONDS);
}else{
throw new TokenException("token过期或失效,请重新登录","tokenError");
}
// 验证 token
verifyToken(token);
logger.info("请求的URL==>"+httpServletRequest.getRequestURI());
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 {
}
public static JSONObject verifyToken(String token) {
DecodedJWT jwt = null;
JSONObject resultMap = new JSONObject();
resultMap.put("tokenExpires", false);
resultMap.put("illegalToken", false);
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(CommonData.SECRET)).build();
jwt = verifier.verify(token);
//失效时间
Date expiresDate = jwt.getExpiresAt();
//token生成时间
Date issuedDate = jwt.getIssuedAt();
jwt.getIssuer();
//头信息
Map<String, Claim> map = jwt.getClaims();
resultMap.put("head", map);
resultMap.put("expiresDate", expiresDate);
resultMap.put("issuedDate", issuedDate);
} catch(JWTDecodeException e){
resultMap.put("illegalToken", true);
logger.error("token校验异常,token非法"+e.getMessage());
throw new TokenException("token校验异常,token非法","tokenError");
} catch (TokenExpiredException e) {
resultMap.put("tokenExpires", true);
logger.error("token校验异常,'{}'已经失效'{}'",token,e.getMessage());
throw new TokenException("token过期或失效,请重新登录","tokenError");
}
return resultMap;
}
}
三、实现WebMvcConfigurer将拦截器加入WebMvc配置
package com.gstanzer.supervise.jwt;
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;
import java.util.ArrayList;
import java.util.List;
/**
* 新建Token拦截器
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludeUrl = new ArrayList<String>();
excludeUrl.add("/swagger-ui/index.html");
excludeUrl.add("/swagger-resources/**");
excludeUrl.add("*/csrf");
excludeUrl.add("/error");
excludeUrl.add("**/api-docs");
excludeUrl.add("**/v2/api-docs");
excludeUrl.add("/v2/api-docs");
excludeUrl.add("/bw-clnt-supervise-service/v2/api-docs");
excludeUrl.add("/v2/consumerDevice/**");
excludeUrl.add("/v2/vatToken/**");
excludeUrl.add("/api/**");
excludeUrl.add("/webjars/springfox-swagger-ui/**");
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**").excludePathPatterns(excludeUrl); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();// 自己写的拦截器
}
}
四、新增token放行注解
package com.gstanzer.supervise.jwt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}