说明
背景:项目采用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);@Resourceprivate CtgRedisUtil redisUtils;// @Resource
// private RedisUtils redisUtils;@Value(("${token.expire.time}"))private Integer expireTime;@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {// 从 http 请求头中取出 tokenString 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");}// 验证 tokenverifyToken(token);logger.info("请求的URL==>"+httpServletRequest.getRequestURI());return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic 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 {@Overridepublic 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 注解 决定是否需要登录}@Beanpublic 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;
}