RESTful API 使用 JWT 做无状态的身份认证

JWT 设计

RESTful 架构的前后端,经常会使用 Token 令牌做身份验证,JWT(JSON Web Token) 是一种简单易用的 Token 标准,适合在应用中做无状态的 API 身份认证。

jwt 由 Header、Payload、Signature 三部分组成,使用. 分割开,一个 JWT 形式:

1
Header.Payload.Signature

这三部分分别对应的是加密算法、携带的用户信息、加密后的字符串(签名)。

Jwt 自带签名,能够防止伪造或篡改,但要防止 token 被窃取还要配合 https 使用。

下面我们用 Jwt 开发一个前后端交互系统。

JWT 服务端

这里使用 jjwt 开源库生成 token

创建 RESTful 接口

Server 端基于 SpringBoot 开发,提供生成 token 和校验 token 的接口:

1
2
3
4
5
6
@PostMapping("/login")
Back login(@RequestParam String name,
@RequestParam String pwd);

@GetMapping("/user")
Back getUser(@RequestHeader String jwt);
  • 登录或注册接口:客户端提交用户名密码,服务端完成登录或注册逻辑,然后生成 jwt 令牌并返回给客户端

  • 用户信息接口:客户端将 token 放在请求头,服务端校验是否合法,然后再从 MySQL 中查询并返回用户信息

  • 服务端无需存储 jwt 令牌,通过特定的算法和密钥校验 token,同时取出 Payload 中携带的用户 ID,减少不必要的数据库查询

  • 本例中设置 jwt 有效期为 3 天,服务端每次都会自动校验 token 是否过期,如果过期就直接抛出异常,客户端需要重新申请 token

Token 统一校验

业务相关接口一般都需要 token 验证,这就应该把验证逻辑放在一个统一的切面层完成,Spring 的 AOP 正好满足这一需要。

这里实现 Spring 的拦截器 HandlerInterceptor 接口,在 Controller 方法执行之前拦截需要鉴权的请求,验证 token 是否合法,合法就放行,不合法则直接抛出异常拦截请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JwtInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
//在请求处理之前校验token
String token = httpServletRequest.getHeader("jwt");
return Util.verify(token);
}

@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

}

@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

}
}

然后在 WebMvcConfigurerAdapter 中添加此拦截器,就可以实现 Token 统一校验了。

JWT 客户端

Android 客户端使用 Retrofit 做 REST 请求。

  • 登录或注册接口:提交用户名密码,服务端返回 jwt 令牌:

  • 用户信息接口:客户端将 token 放在请求头,服务端校验通过即返回用户信息

  • 客户端在本地存储 token 以后就能免登陆

JWT 的缺陷

JWT 使用起来虽然简单方便,但它存在一个缺陷,即服务端无法主动注销 token,如果修改秘钥会让所有 token 失效,代价太大,通常只是被动等待 token 过期失效,所以 jwt 在安全性上不及 session,实际开发中应合理设置过期时间。

如果要让服务端能够注销 token,就要在服务端维护 token 状态,但这又回到 session 机制了。

JWT 这个缺陷决定了它更适合用在短期的 token 验证场景中,或者也可以结合 session 做长短期双重验证,弥补 session 的一些不足。

项目源码

Jwt 服务端

https://github.com/yunTerry/JWT-Server

Jwt 客户端

https://github.com/yunTerry/JWT-Android