(本文面向后端入门同学或是对鉴权欠缺研究的道友)
<dependency> <groupId>io.github.steadon</groupId> <artifactId>utils</artifactId> <version>2.1.3</version> </dependency>然后我们完成最基本的签名返回给前端:
// 配置token载荷中的字段 @Data @AllArgsConstructor public class LoginBackVo { @Token private Integer uid; @Token private String phone; } .... // 注入工具依赖 @Autowired private JWTUtils jwtUtils; .... // 签发token并返回前端 LoginBackVo backVo = new LoginBackVo(user.getId(), phone); return CommonResult.success(new TokenResultC(jwtUtils.createToken(backVo)));
那么至此我们就完成了鉴权最基本的操作,接下来我们继续探讨当前端带着token来请求接口时我们如何高效优雅地处理(本文假设token被放在了Header.Authorization字段中)。
@Component public class JwtInterceptor implements HandlerInterceptor { @Autowired private JWTUtils jwtUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 从请求头中获取 JWT Token String token = request.getHeader("Authorization"); // 浏览器option预检查放行 if (!(handler instanceof HandlerMethod)) { return true; } // 验证token if (!jwtUtils.checkToken(token)) { // 设置 HTTP 状态码为 401 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } // 验证通过,获取uid并向下传递 LoginBackVo loginBackVo = jwtUtils.parseToken(token, LoginBackVo.class); request.setAttribute("uid", loginBackVo.getUid()); request.setAttribute("phone", loginBackVo.getPhone()); return true; } }如此我们只需要为拦截器配置拦截范围即可完成鉴权:
@Resource private JwtInterceptor jwtInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 注册拦截器,并配置拦截路径 registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/api/login") // 排除指定请求 }同样我们来分析一下:
@Aspect @Component public class PermissionAspect { @Autowired private HttpServletResponse response; /* 对相关业务模块织入前置通知 */ @Before("execution(* com.example.test.service.impl.OrderServiceImpl.*(..))") public void beforeRequest() { checkPermission(); } /* 二次校验token中的 permission */ private void checkPermission() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); Byte permission = (Byte) request.getAttribute("permission"); // 鉴别条件灵活处理 if (permission == 0) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); throw new UnauthorizedException("no permission!"); } } }显然aop本身只是一种修饰,并不能直接结束请求响应前端401,因此我们在此处抛出了一个自定义的异常,目的是在全局异常处理中捕获异常终止请求:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UnauthorizedException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) // This ensures that the HTTP status is set to 401 public ResponseEntity<String> handleUnauthorizedException(UnauthorizedException ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.UNAUTHORIZED); } }同样我们也分析一下:
Integer uid = (Integer) request.getAttribute("uid");
虽然这种情况并不多,但是对于完美主义者来说是不够的,为此我又打算再抽象出一个全局类去获取这些载荷字段,当我想使用这些字段时可以直接调用静态方法获取而不必携带request参数,我想ThreadLocal应该可以做到。
public class TokenHandler { private static final ThreadLocal<String> username = new ThreadLocal<>(); public static void set(String payload) { username.set(payload); } public static String get() { return username.get(); } public static void remove() { username.remove(); } } .... // 将载荷存入ThreadLocal AuthorizationParam authorizationParam = jwtUtils.parseToken(token, AuthorizationParam.class); TokenHandler.set(authorizationParam.getUsername()); .... @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 清除 ThreadLocal 中的数据(不清除会导致数据错乱) TokenHandler.remove(); }到此为止,我认为已经满足的对优雅的认定了,我们可以通过如下方式获取参数:
String username = TokenHandler.get();显而易见,我们彻底实现了解耦,并且完成了一个高可用的鉴权模块!期待能在评论区看到你的留言!也期待每一条技术方面的建议!