JWT 配合拦截器 / 自定义注解 实现鉴定权限

概述

JWT 主要是一种验证用户身份的方式.相对于 session 而言,服务器不需要再进行保存一个 session 的会话信息. 而通过根据自定义的加密方式去加密用户的相关数据当做一个令牌(token).

只看它的名字便知道,数据采用的是 json 格式. 一般的,分为三个部分:

  • Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
  • Payload 负载 (我们所有的信息都存放在这里)
  • Signature 签名/签证

简单的说,我们通过用户所提供的信息去生成一个令牌,而这个令牌是基于我们的私钥进行去创建的.用户如果拿不到我们的秘钥,只要修改其中一个字符,令牌即失效.并且 token 中会去保存令牌失效时间.如果时间到了令牌也会失效.

引入

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.5.0</version>
</dependency>

JWT 工具类

public class JwtUtil {

    /**
 * 过期时间为一天
 * TODO 正式上线更换为15分钟
 */
    private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;

    /**
 * token私钥
 */
    private static final String TOKEN_SECRET = "2333";

    /**
 * 生成签名,15分钟后过期
 *
 * @param userName
 * @param userLevel
 * @return
 */
    public static String sign(String userName, String userLevel) {
        //过期时间
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        //私钥及加密算法
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        //设置头信息
        HashMap<String, Object> header = new HashMap<>(2);
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        return JWT.create().withHeader(header).withClaim("userName", userName)
            .withClaim("userLevel", userLevel).withExpiresAt(date).sign(algorithm);
    }
    //验证 token 是否有效
    public static int verity(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return 0;
        } catch (IllegalArgumentException | JWTVerificationException e) {
            return 1;
        }

    }
    //获得 token 中的载荷信息
    public JSONObject getPayload(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            String payload = jwt.getPayload();

            return (JSONObject) JSON.parse(Base64.getDecoder().decode(payload));

        } catch (IllegalArgumentException | JWTVerificationException e) {
            return null;
        }
    }
}

sign 方法中可以看到,我们通过withClaim方法直接将我们要写进 token 的信息加载了进去.并且通过我们的私钥进行加密.那么如果用户想要伪造令牌就需要得到这个私钥~ 因此用户即使随便更改一个字符那么所对应的令牌不可能成立.所以 token 是不可伪造的~

因此当用户登录成功之后我们可以生成一个 token 发送给前端,然后每次发起请求的时候,前端在请求的协议头中添加 token,后端在得到请求的时候去证伪就可以了~

证伪

以下是编写一个接口,一般情况下我们是通过HttpServletRquest参数 去获得协议头最后获得 token 进行鉴定.

/*
鉴定 token 是否伪造.
*/
@GetMapping("/getTokenVerity")
public int getTokenVerity(@RequestParam("token") String token) {

    return JwtUtil.verity(token);

}

拦截器与注解

学会了前面的方法,后期在项目中我们不可能每编写一个接口之后都要去证伪 token 如果这样,那么代码冗余也太高了.所以我们可以直接通过拦截器去拦截方法,在方法执行前就去判断 token 是否存在或是否过期.

但是我们并不是每一次都需要去判断,所以可以通过注解进行声明这个方法需要去验证.

注解

@Target(ElementType.METHOD)  //只作用于方法之上
@Retention(RetentionPolicy.RUNTIME)  //在运行时生效
public @interface TokenVerify {
}

拦截器

我们需要去实现HandlerInterceptor 接口.

public class AuthenticationInterceptor implements HandlerInterceptor {

}

该接口一共有三个方法,分别是:
1.boolean preHandle ()
主要含义为,要执行那个方法的时候可以通过这个方法做一些预先处理.拥有 HttpServletRequestHttpServletResponse ,以及涵盖对应类对象的参数.
当返回 true 是通过该处理器,反之是停止该请求.
2.void postHandle()
即将返回视图层的时候进行调用,该方法在preHandler 方法的基础上拥有modeAndView 参数.
3.void afterCompletion()
当视图解析完毕后执行,主要做请求完毕的回调方法.

看完以上三个方法之后,思路很明显.我们需要在 preHandle 加入判断方法是否存在 TokenVerify 注解.如果存在那么就通过 HttpServletRequest 方法去获得协议头中的 token ,然后再去判断是否存在.
如果不存在,我们就通过HttpServletResponse去设置返回.

preHandler

首先我们需要编写一下代码:

if(!(handler instanceof HandlerMethod)){
    return true;
}

instanceof 的意思是,判断 Handler 参数是否属于 handlerMethod类. 主要原因是,spring 在初始化阶段会将所有的 controller 方法进行组建映射,而如果是控制器方法必然会使用 handlerMethod 类进行包装.如果当前拦截到的目标不属于这个类,那也没有拦截的必要了,所以直接返回 true 进行放行.

String token = request.getHeader("token");
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(TokenVerify.class)){
    if (token == null || JwtUtil.verity(token) == 1){
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().append(new Msg(1,"身份无效或已过期!",null).toJson());
        return false;

    }

}

接下来通过 handlerMethod 去获得我们的方法对象,通过isAnnotationPresent去判断该方法是否加载了 TokenVerify 注解.如果拥有便从协议头中去获得 token 并对其证伪.

创建拦截器配置类

当我们写完拦截器之后,需要再编写一个配置类使其启动.

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
            .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }

}
最后修改:2021 年 10 月 14 日 05 : 27 PM
如果觉得我的文章对你有用,请随意赞赏