JWT 全称 Json Web token,它将用户信息加密到 token 当中,服务端不保存任何用户信息。当服务端发送请求时,服务器通过保存的密钥验证 token 的正确性,正确则放行。
优点
使用简洁,数据量小,传输速度快,可以直接在 HTTP header 中发送
token 中包含了用户所需要的信息,避免了多次查询数据库
token 以 JSON 加密的形式在客户端存储,不需要在服务端保持会话
缺点
已经颁布的令牌无法作废
不易处理数据过期的问题
token 主要由 3 部分构成,分别为 header、payload 以及 signature,三部分之间以`.``分割,例如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJhdWQiOiJhZG1pbiIsImV4cCI6MTY2NDQxMTI4Mn0
.cNvdGNNsNl8UJX_PIz40h9iJtDWexoymnC3h_b42iMA
header
声明类型,标识这是一条 JWT 信息
声明 JWT 加密的算法
payload
signature
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.10.3version>
dependency>
配置@LoginToken
注解,标识需要进行 JWT 验证才能访问的方法
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
boolean required() default true;
}
通过拦截器对请求进行拦截,判断请求访问的方法和注解,根据条件对 token 进行验证
tips:ApplicationContextUtil 是上下文工具类,可自行搜索…
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取业务类
UserService userService = ApplicationContextUtil.getBean(UserService.class);
// 获取请求头中的 token
String token = request.getHeader("token");
// 如果不是映射到方法的,直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 获取请求的方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 验证是否存在需要验证用户权限的注释
if (method.isAnnotationPresent(LoginToken.class)) {
if (method.getAnnotation(LoginToken.class).required()) {
// 执行认证
if (token == null) {
log.error("token 不存在,请重新登录");
throw new RuntimeException("token 不存在,请重新登录");
}
// 获取 userId
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException e) {
log.error("token 解码失败");
throw new RuntimeException("token 解码失败");
}
// 根据 userId 查询 User
User user = userService.getUser(userId);
if (user == null) {
log.error("用户不存在,请重新登录");
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
log.error("token 校验失败");
throw new RuntimeException("token 校验失败,请重新登录");
}
return true;
}
}
return true;
}
}
拦截器配置类
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 注册 JWT 拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor()).addPathPatterns("/**");
}
}
全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) {
String msg = e.getMessage();
if (msg == null || "".equals(msg)) {
msg = "服务器出错";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 50000);
jsonObject.put("message", msg);
return jsonObject;
}
}
构造tokenService
,对用户的信息执行注册,返回对应用户的 token
@Service
public class TokenService {
/**
* 设置过期时间
*/
private static final long EXPIRE_TIME = 5 * 60 * 1000;
/**
* 获取 token
*/
public String getToken(User user) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
return JWT.create()
// 将 userId 保存在 token 中
.withAudience(user.getUserId())
// 设置过期时间
.withExpiresAt(date)
// 将 password 设置为 token 的密钥
.sign(Algorithm.HMAC256(user.getPassword()));
}
}
构造userService
,模拟从数据库中查询对应用户信息的过程
@Service
public class UserService {
/**
* 模拟查询参数
*/
private final static String PARAM = "admin";
/**
* 根据 id 查询 User
*/
public User getUser(String userId) {
if (PARAM.equals(userId)) {
return User.builder()
.userId(userId)
.username("admin")
.password("admin")
.build();
}
return null;
}
/**
* 根据用户名查询 User
*/
public User getUser(String username, String password) {
if (PARAM.equals(username)) {
return User.builder()
.userId("admin")
.username(username)
.password(password)
.build();
}
return null;
}
}
@RestController
@RequestMapping("/JWT")
public class LoginController {
@Resource
private UserService userService;
@Resource
private TokenService tokenService;
/**
* 登录
*/
@PostMapping("/sign")
public Object sign(String username, String password){
JSONObject jsonObject = new JSONObject();
User user = userService.getUser(username, password);
if (user == null) {
jsonObject.put("message", "登录失败!");
} else {
String token = tokenService.getToken(user);
jsonObject.put("token", token);
jsonObject.put("user", user);
}
return jsonObject;
}
/**
* 验证登录
*/
@LoginToken
@PostMapping("/getMessageWithToken")
public String getMessageWithToken(){
return "已通过验证,允许获取信息~";
}
/**
* 验证登录
*/
@LoginToken(required = false)
@PostMapping("/getMessageWithoutToken")
public String getMessageWithoutToken(){
return "无需验证即可获取对应信息~";
}
}
首先测试不携带 token 访问getMessageWithToken
与getMessageWithoutToken
接口
getMessageWithToken
未携带 token,禁止访问
getMessageWithoutToken
不需要 token 认证即可访问
访问sign
接口执行注册,获得返回的 token
携带 token 重新访问getMessageWithToken
,可正确执行访问