Gateway统一鉴权,透传userId(个人项目复盘) 这是在做完一个完整微服务项目后对鉴权功能的一个复盘笔记。也是为了对后面的面试做准备。首先鉴权是指两个操作。一个是身份鉴别一个是权限校验。身份鉴别鉴别我们验证码或者密码是否正确。权限校验我们通过 RBAC Role-base Access Control基于角色的权限控制来校验用户是否具备该操作权限。鉴权我使用到的是Sa-token, Sa-token 是一款轻量级的 Java 权限认证框架。该框架可以帮助我很快的实现 登录认证、权限校验的功能使我可以更加专注于微服务模块的业务开发。想了解Sa-token其他能得可以访问其网页https://sa-token.cc/下面就让我们来顺一下这个鉴权业务逻辑吧。以登录请求为例首先前端发来 登录请求。请求为到达 Gateway Gateway会将请求路由到相应的服务中。在此之前 请求会先后经过两个过滤器。分别是 SaReactorFilter、AddUserId2HeaderFilter。SaReactorFilter 除了不拦截白名单中的登录和发送验证码请求外拦截所有请求。当遇到登录请求 SaReactorFilter 会对其放行。public SaReactorFilter getSaReactorFilter() { return new SaReactorFilter() // 拦截地址 .addInclude(/**) /* 拦截全部path */ // 鉴权方法每次访问进入 .setAuth(obj - { log.info( SaReactorFilter, Path: {}, SaHolder.getRequest().getRequestPath()); // 登录校验 SaRouter.match(/**) // 拦截所有路由 .notMatch(/auth/login) // 排除登录接口 .notMatch(/auth/verification/code/send) // 排除验证码发送接口 .check(r - StpUtil.checkLogin()) // 校验是否登录 ; // 权限认证 -- 不同模块, 校验不同权限 SaRouter.match(/auth/logout, r - StpUtil.checkRole(common_user)); // 更多匹配 ... */ }) // 异常处理方法每次setAuth函数出现异常时进入 .setError(e - { // return SaResult.error(e.getMessage()); // 手动抛出异常抛给全局异常处理器 if (e instanceof NotLoginException) { throw new NotLoginException(e.getMessage(), null, null); } else if (e instanceof NotPermissionException || e instanceof NotRoleException) { throw new NotPermissionException(e.getMessage()); } else { throw new RuntimeException(e.getMessage()); } }) ; }随后经过 AddUserId2HeaderFilter , 由于第一次登录前端并没有携带token, 也就是没法获取用户Id, 抛出异常执行放行。public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info( TokenConvertFilter); // 用户 ID Long userId null; try { // 获取当前登录用户的 ID userId StpUtil.getLoginIdAsLong(); } catch (Exception e) { // 若没有登录则直接放行 return chain.filter(exchange); } log.info(## 当前登录的用户 ID: {}, userId); Long finalUserId userId; ServerWebExchange newExchange exchange.mutate() .request(builder - builder.header(GlobalConstants.USER_ID, String.valueOf(finalUserId))) // 将用户 ID 设置到请求头中 .build(); return chain.filter(newExchange); }放行后将请求路由到了Auth服务中Auth服务执行登录操, 并返回Token。这时前端获得了token下次就可以访问该用户具有权限的功能。那么我就走一下有了token后是如何执行的鉴权。先进入到SaReactorFilter执行登录操作并校验权限是否合法。合法后 AddUserId2HeaderFilter 会将用户 userId 存入到请求同中然后将新的携带userId请求头的 HTTP 请求路由到对应的服务中去这样就可将用户userId透传给下游服务了。在请求到达对应服务前还会经过一个过滤器HeaderUserId2ContextFilter 将用户userId 存储到 ThreadLocal 中。方便直接获取userId。public class HeaderUserId2ContextFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 从请求头中获取用户 ID String userId request.getHeader(GlobalConstants.USER_ID); // 判断请求头中是否存在用户 ID if (StringUtils.isBlank(userId)) { // 若为空则直接放行 chain.doFilter(request, response); return; } log.info( 设置 userId 到 ThreadLocal 中 用户 ID: {}, userId); LoginUserContextHolder.setUserId(userId); try { chain.doFilter(request, response); } finally { // 一定要删除 ThreadLocal 防止内存泄露 LoginUserContextHolder.remove(); log.info( 删除 ThreadLocal userId: {}, userId); } } }以下是我整理的鉴权和各个过滤器的流程。第一次写文章还不会写先简单写一些试试要去吃饭了好饿本文章还有一些没有讲到的欢迎评论区讨论~