导入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 小喻同学
*/
@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* @addPathPatterns 配置拦截的路径
* 如果写成 .addPathPatterns("/**")
* 那么就会拦截所有的controller
* **/
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/admin","/admin/*");
}
@Bean
public 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 泛指组件,当组件不好归类的时候,一般使用这个注解进行标注
@Component
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private AdminService adminService;
@Override
public 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权限认证");
// 请求头中没有找到token
if(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;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public 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去请求接口