导入java-jwt包
<!--jwt for java 权限认证--><!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.2</version></dependency>
编写jwt工具类
package com.example.demo2.springbootmybatis.utils;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.interfaces.Claim;import com.auth0.jwt.interfaces.DecodedJWT;import com.example.demo2.springbootmybatis.exception.TokenUnavailableException;import java.util.Calendar;import java.util.Date;public class JwtUtils {/*** 签发对象:用户的id* 签发时间:现在* 有效时间:30分钟* 载荷内容:用户名和用户编号* 加密秘钥:id+一段字符串* */public static String createToken(Integer userId,String userName,String userNumber){Calendar nowTime = Calendar.getInstance();nowTime.add(Calendar.MINUTE,30);Date expireDate = nowTime.getTime();/*** withAudience: 签发对象* withIssuedAt: 签发时间* withExpiredAt: 有效时间* withClaim: 荷载内容* sign: 签名* */return JWT.create().withAudience(userId.toString()).withIssuedAt(new Date()).withExpiresAt(expireDate).withClaim("userName",userName).withClaim("userNumber",userNumber).sign(Algorithm.HMAC256(userId + "HelloWorld"));}/*** 校验 token 的合法性* @param token token字符串* @param secret 用户id* @throws TokenUnavailableException* **/public static void verifyToken(String token,String secret) throws TokenUnavailableException {DecodedJWT jwt = null;try {JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret + "HelloWorld")).build();// 如果校验过程中出现问题,会抛出TokenUnavailableException异常jwt = verifier.verify(token);} catch (Exception e){throw new TokenUnavailableException(4003,e.getMessage());}}/*** 获取签发对象* @param token token字符串* @throws TokenUnavailableException* */public static String getAudience(String token) throws TokenUnavailableException {String audience = null;try{audience = JWT.decode(token).getAudience().get(0);System.out.println(audience);}catch (JWTDecodeException e){throw new TokenUnavailableException(4002,e.getMessage());}return audience;}/*** 通过载荷名字获取载荷的值*/public static Claim getClaimByName(String token, String name){return JWT.decode(token).getClaim(name);}}
注解类编写
免验证注解
@PassToken: 表明该接口可以跳过验证
拦截器的编写
拦截器配置类
package com.example.demo2.springbootmybatis.config;import com.example.demo2.springbootmybatis.service.impl.JwtAuthenticationInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author 小喻同学*/@Configurationpublic class JwtInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {/*** @addPathPatterns 配置拦截的路径* 如果写成 .addPathPatterns("/**")* 那么就会拦截所有的controller* **/registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/admin","/admin/*");}@Beanpublic JwtAuthenticationInterceptor authenticationInterceptor() {return new JwtAuthenticationInterceptor();}}
拦截器
package com.example.demo2.springbootmybatis.service.impl;import com.example.demo2.springbootmybatis.annotation.PassToken;import com.example.demo2.springbootmybatis.entiy.Admin;import com.example.demo2.springbootmybatis.exception.AdminNotExistException;import com.example.demo2.springbootmybatis.exception.NeedToLoginException;import com.example.demo2.springbootmybatis.service.AdminService;import com.example.demo2.springbootmybatis.utils.JwtUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;// @Component 泛指组件,当组件不好归类的时候,一般使用这个注解进行标注@Componentpublic class JwtAuthenticationInterceptor implements HandlerInterceptor {@Autowiredprivate AdminService adminService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {String token = request.getHeader("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()){return true;}}else{System.out.println("被jwt拦截了,需要进行token权限认证");// 请求头中没有找到tokenif(token == null){throw new NeedToLoginException(4001,"需要先登录");}String userId = JwtUtils.getAudience(token);Admin admin = adminService.getOne(Integer.valueOf(userId));if(admin == null){System.out.println("AdminNotExist");throw new AdminNotExistException(4004,"不存在该管理员");}JwtUtils.verifyToken(token,userId);String userName = JwtUtils.getClaimByName(token, "userName").asString();String userNumber = JwtUtils.getClaimByName(token, "userNumber").asString();System.out.println("name:" + userName);System.out.println("number:" + userNumber);request.setAttribute("userName",userName);request.setAttribute("userNumber",userNumber);return true;}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}
拦截器的逻辑大概如下
- 目标方法是否有注解?如果有
@PassToken注解就不用执行后面的验证,直接放行,否则需要进行验证。 - 请求头中有没有token?没有的话直接返回错误。
 - 从token中获取签发的对象。从数据库中查询这个用户是否存在(有可能是客户端造假,也有可能是这个用户的账户被冻结了)。查询的话直接调用对应的Service层
 - 检验Jwt的有效性。判断令牌是否无效或者过期了。
 - 校验成功,把载荷内容获取到,可以在controller中使用。
 

去Controller上测试一下
postman测试
不附带token去请求接口
使用过期的token去请求接口
使用正确的token去请求接口
