文章目录
前言
研究生期间做了三年数字大屏,从来没写过登录以及权限校验相关的功能。谁曾想今年刚毕业参加工作的第一项任务就是做一个带登录界面的数字大屏😓,索性就学了一下权限校验相关的内容,在此记录一下。
目前比较常用的两种权限校验方式就是JWT或者常规token+redis的方式(session和cookie就不说了),两种方式各有优劣,适用场合也不同,我选择JWT主要是图个方便,而且项目比较小,偏练手性质,生产环境下使用还是要权衡好利弊。
一、JWT是什么?
概念我就不赘述了,大家可以参考一下阮一峰老师的JSON Web Token 入门教程
二、使用步骤
1.引入 java-jwt 依赖
在 springboot 项目的 pom.xml 文件中引入如下依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
项目具体细节可访问项目地址查看。
2.创建 jwt 工具类
创建 jwt 工具类用于创建及验证 jwt:
@Component
public class JWTUtils {
// 用于加密的私钥
@Value("${jwt.secret-key}")
private String secretKey;
// jwt有效期
@Value("${jwt.expire-period}")
private Long expirePeriod;
/**
* 创建 jwt
*/
public String getJWT(String userId) {
// 计算过期时间
Date now = new Date();
Date expireTime = new Date(now.getTime() + expirePeriod);
// 生成 JWT
// JWT 携带的属性可以按需选择
// 此处只设置了受众和过期时间
return JWT
.create()
.withAudience(userId)
.withExpiresAt(expireTime)
.sign(Algorithm.HMAC256(secretKey)); // 根据密钥设置签名, 算法也可以按需选择
}
/**
* 解析 jwt
*/
public Map<String, String> parseJWT(String jwt) {
try {
Map<String, String> map = new HashMap<>();
DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(secretKey))
.build().verify(jwt);
// 创建时设置的属性都可以读取
// 此处只读取了 Audience 作为演示
String userId = decodedjwt.getAudience().get(0);
map.put("userId", userId);
return map;
}
// 创建时如果设置了过期时间, 在解析时发现 jwt 过期即会抛出该异常
catch (TokenExpiredException e) {
throw new TokenExpiredException("jwt 已过期", Instant.now());
}
// JWTVerificationException 是验证时可能出现的各种异常的父类
// 异常部分可以参考官方文档, 很多情况都有覆盖
catch (JWTVerificationException e) {
throw new JWTVerificationException("jwt 解析失败");
}
}
}
3.创建拦截器用于拦截需要进行权限校验的请求
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
@Resource
JWTUtils jwtUtils;
/**
* 权限认证的拦截操作.
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
System.out.println("=======进入权限认证拦截器=======");
// 如果不是映射到方法直接通过, 不进行拦截
if (!(object instanceof HandlerMethod)) {
return true;
}
// 读取请求头中的 jwt
// 这里图方便没有加 Bearer 前缀
String jwt = httpServletRequest.getHeader("Authorization");
// 大部分异常情况都可以在解析时捕获
Map<String, String> map = jwtUtils.parseJWT(jwt);
String userId = map.get("userId");
// 根据 userId 等解析出的属性执行相关操作
return true;
}
}
在这里有一点需要额外注意的,就是如果 preHandle 方法在执行过程中抛出异常(如 jwt 过期或者格式错误等)或最终返回 false,那就不会正常返回给前端相应的 json 格式的提示信息,返回的是 html。可以通过全局异常处理或者在 preHandle 中编写额外的处理逻辑来避免这一点。
4.注册拦截器以拦截请求
@Configuration
public class AuthWebMvcConfigurer implements WebMvcConfigurer {
@Resource
AuthHandlerInterceptor authHandlerInterceptor;
/**
* 给除了 /login 的接口都配置拦截器, 拦截转向到 authHandlerInterceptor
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authHandlerInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
5.全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 针对所有 RuntimeException 类型异常的拦截处理器
* 发生异常后都会转到这里执行
* 可以根据实际需要将异常类型进一步细分, 以更加精准地处理各类异常
*/
@ExceptionHandler(RuntimeException.class)
public Result runtimeExceptionHandler(RuntimeException e) {
System.out.println(e.getMessage());
// Result 类相关的就是你自定义的标准 Json 返回格式, 此处不再给出
return ResultFactory.buildFailResult(ResultCode.INTERNAL_SERVE_ERROR);
}
}
6.编写测试控制器
@CrossOrigin
@RestController
public class LoginController {
@Resource
JWTUtils jwtUtils;
@GetMapping("/login")
public String login(@RequestParam(name = "userName") String userName,
@RequestParam(name = "password") String password) {
if (userName.equals("admin") && password.equals("1234")) {
return jwtUtils.getJWT(userName);
} else {
return "用户名或密码错误";
}
}
@GetMapping("/test")
public String test() {
// 权限校验拦截器会在进入该方法前执行相关验证逻辑
System.out.println("执行到这里就成功了!");
return "请求成功";
}
}
7.测试
使用 postman 或者其他接口测试工具进行测试:
- 登录获取 jwt
- 使用该 jwt 在有效期内发送 test 请求,请求成功
- 使用该 jwt 但不在有效期内发送 test 请求,请求失败
总结
springboot 通过 java-jwt 整合 JWT 还是比较便捷的,因为也是初次接触所以实现的比较简陋,关于权限验证以及延申出来的内容还有待深入学习。
第一次上班写博客,发现还挺消磨时光的哈哈哈✌️