概述
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 ()
主要含义为,要执行那个方法的时候可以通过这个方法做一些预先处理.拥有 HttpServletRequest 和 HttpServletResponse ,以及涵盖对应类对象的参数.
当返回 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();
}
}
本页的评论功能已关闭