Skip to content

网关认证与权限转发

Molan Cloud 在网关层实现了认证和权限转发机制,权限校验由后端服务通过 AOP 完成。

架构设计

过滤器链

网关使用两个核心过滤器:

  1. GatewayAuthFilter - 认证过滤器(GlobalFilter,Order=1)

    • 验证 Token 有效性
    • 解析用户信息
    • 白名单检查
    • 获取用户权限码
    • Token 续期
  2. PermissionForwardGatewayFilter - 权限转发过滤器(Per-route GatewayFilter)

    • 从 exchange attribute 获取用户权限码
    • 按前缀过滤当前服务所需的权限码
    • 通过 Header 转发给后端服务

处理流程

请求进入

GatewayAuthFilter(认证 + 加载权限)
  ↓ Token 验证
  ↓ 白名单检查
  ↓ 用户信息解析
  ↓ 获取用户权限码 → 存入 exchange attribute
  ↓ 自动续期

PermissionForwardGatewayFilter(权限转发)
  ↓ 读取 exchange attribute 中的权限码
  ↓ 按前缀过滤
  ↓ 写入 USER_PERMISSIONS Header

后端服务
  ↓ PermissionAopAspect(AOP 权限校验)

认证过滤器

核心功能

GatewayAuthFilter 负责验证用户身份并加载权限信息。

处理逻辑:

  1. WebService 绕过

    java
    if (exchange.getRequest().getURI().getPath().contains("webservice")) {
        return chain.filter(exchange);
    }
  2. Swagger 开发模式

    java
    if (swaggerEnabled && originHeaders.contains("Knife4j")) {
        return chain.filter(exchange);
    }
  3. 无 Token 请求

    • 白名单路径:放行
    • 非白名单路径:返回未登录
  4. Token 验证

    • 从 Redis 获取 Token 信息(AUTH_TOKEN:{token}
    • 检查 Token 是否存在
    • 检查是否被踢出(kickout=1
  5. 获取权限码

    java
    Set<String> permissions = reactiveRedisTemplate.opsForSet()
        .members(AuthConst.AUTH_PERMISSION_CACHE_PREFIX + token)
        .collect(Collectors.toSet());
    // 存入 exchange attribute,供 PermissionForwardGatewayFilter 使用
    newExchange.getAttributes().put(ATTR_USER_PERMISSIONS, permissions);
  6. 自动续期

    java
    if (expireInSeconds > 0 && expireInSeconds < effectiveSeconds / 2) {
        reactiveRedisTemplate.expire(AUTH_TOKEN:{token}, effectiveSeconds);
        reactiveRedisTemplate.expire(AUTH_PERMISSION:{token}, effectiveSeconds);
    }
  7. 用户信息传递

    java
    String encodeUser = URLEncoder.encode(userJson, StandardCharsets.UTF_8);
    newRequest.header(AuthConst.HEADER_USER_INFO, encodeUser);

白名单配置

配置类: GatewayAuthProperties

配置前缀: molandev.security

内部白名单(innerWhiteUris):

java
List<String> innerWhiteUris = ListUtils.toList(
    "/sys/login",
    "/sys/captcha",
    "/sys/area/treeSimple",
    "/sys/area/children",
    "/sys/strategy/user/info",
    "/auth/qrcode/verify",
    "/file/upload",
    "/file/preview/**",
    "/file/download/**",
    "/*/v3/api-docs",
    "/v3/api-docs",
    "/doc.html",
    "/v3/api-docs/swagger-config",
    "/webjars/**"
);

自定义白名单(whiteUris):

yaml
molandev:
  security:
    white-uris:
      - /your/custom/path

合并规则: 自定义白名单与内部白名单合并,使用 AntPathMatcher 匹配。

匹配规则:

  • 使用 AntPathMatcher 进行路径匹配
  • 支持通配符:***
  • /sys/user/* - 匹配一级路径
  • /sys/user/** - 匹配所有子路径

临时 Token 限制

临时 Token 只能访问特定接口:

配置:

yaml
molandev:
  security:
    temp-uris:
      - /sys/personal/updatePassword    # 修改密码
      - /sys/personal/info              # 个人信息

检查逻辑:

java
if (UserSessionObj.TEMP_FLAG.equals(userSessionObj.getTemp())) {
    if (checkTempUri(path)) {
        return chain.filter(newExchange);
    } else {
        return handleUnauthorized(exchange);
    }
}

权限转发过滤器

核心功能

PermissionForwardGatewayFilter 将用户权限码按前缀过滤后,通过 Header 转发给后端服务。

网关只做认证与权限转发;权限校验由后端 PermissionAopAspect 根据 @HasPermission 注解完成。

处理逻辑:

  1. 获取权限码

    java
    Set<String> userPermissions = exchange.getAttribute(GatewayAuthFilter.ATTR_USER_PERMISSIONS);
  2. 按前缀过滤

    java
    String filtered = permissions.stream()
        .filter(permission -> {
            for (String prefix : permissionPrefixes) {
                if (permission.startsWith(prefix)) {
                    return true;
                }
            }
            return false;
        })
        .collect(Collectors.joining(","));
  3. 写入 Header(明文逗号分隔,与后端 PermissionAopAspect 解析方式一致)

    java
    ServerHttpRequest newRequest = exchange.getRequest().mutate()
        .headers(h -> {
            h.remove(AuthConst.HEADER_USER_PERMISSIONS);
            h.set(AuthConst.HEADER_USER_PERMISSIONS, filtered);
        })
        .build();

路由配置

过滤器工厂: PermissionForwardGatewayFilterFactory

配置格式: PermissionForward=前缀1,前缀2,...

Nacos 路由配置示例:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: base
          uri: lb://base-service
          predicates:
            - Path=/api/sys/**,/api/file/**,/api/task/**,/api/msg/**
          filters:
            - StripPrefix=1
            - PermissionForward=sys,task,msg
        - id: knowledge
          uri: lb://knowledge-service
          predicates:
            - Path=/api/kl/**
          filters:
            - StripPrefix=1
            - PermissionForward=kl

配置说明:

  • PermissionForward=sys,task,msg 表示只传递以 systaskmsg 开头的权限码
  • 不配置前缀时,传递所有权限码
  • 前缀与权限码格式 模块:功能:操作 中的模块部分对应

请求头传递

网关将用户信息和权限码通过请求头传递给后端服务:

传递内容:

java
// GatewayAuthFilter:写入 Token 与用户信息
h.set(AuthConst.HEADER_AUTHORIZATION, token);
h.set(AuthConst.HEADER_USER_INFO, encodeUser);  // URL 编码的用户信息 JSON

// PermissionForwardGatewayFilter:写入权限码
h.set(AuthConst.HEADER_USER_PERMISSIONS, filtered);  // 明文逗号分隔

请求头:

Header说明示例
Admin-Token用户 Tokenuuid-string
USER_INFOURL 编码的用户信息 JSON%7B%22id%22%3A...%7D
USER_PERMISSIONS明文权限码(逗号分隔)sys:user:add,sys:user:edit

后端获取:

java
// 获取当前用户(AuthUtils 内部会对 USER_INFO 做 URLDecoder)
ContextUser user = AuthUtils.getUser();

// 获取权限码(由 PermissionAopAspect 内部处理,业务代码无需关心)

安全过滤器

RemoveHeaderGlobalFilter

全局过滤器,在认证之前剥离客户端伪造的内部鉴权头,必须早于 GatewayAuthFilter 执行。

剥离列表:

Header说明
INNER内部调用标记
USER_INFO用户上下文(仅允许网关写入)
USER_PERMISSIONS权限码(仅允许网关写入)

网关在认证通过后通过 headers.set(...)(先 remove 再 set)写入真实值。业务服务不得对公网暴露;敏感头只信任网关注入结果。

java
// RemoveHeaderGlobalFilter:始终剥离
httpHeaders.remove(HEADER_INNER);
httpHeaders.remove(HEADER_USER_INFO);
httpHeaders.remove(HEADER_USER_PERMISSIONS);

// GatewayAuthFilter / PermissionForward:仅 set,不 add
h.remove(HEADER_USER_INFO);
h.set(HEADER_USER_INFO, encodeUser);

单体模式支持

单体模式使用 LocalAuthFilter 实现认证功能,权限校验由 PermissionAopAspect 完成。

LocalAuthFilter

差异:

  • 使用 Servlet Filter(OncePerRequestFilter)而非 Gateway Filter
  • 同步代码而非响应式
  • 直接从 Redisson 获取数据
  • 不负责权限校验,仅做认证

核心逻辑:

java
@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain filterChain) {
    // 1. Swagger 绕过
    // 2. 提取 Token
    // 3. 从 Redis 获取用户会话
    // 4. 白名单检查
    // 5. 踢出检查
    // 6. 临时 Token 检查
    // 7. 正常用户:设置上下文 + 自动续期
    // 8. 权限校验由 PermissionAopAspect 负责
}

双模配置

通过 AuthAutoConfiguration 根据配置自动注册对应的 Bean:

配置项:

yaml
molandev:
  security:
    mode: LOCAL          # LOCAL 或 GATEWAY
    check-permission: true  # 是否启用权限校验,默认 true

LOCAL 模式:

  • 注册 LocalAuthFilter(认证)
  • 注册 PermissionAopAspect(权限校验,从 Redis 读取权限码)

GATEWAY 模式:

  • 不注册 LocalAuthFilter(认证由网关完成)
  • 注册 PermissionAopAspect(权限校验,从 Header 读取权限码)

特殊处理

1. WebSocket 支持

WebSocket 连接在单体模式下返回 401 状态码:

java
if (isWebSocket) {
    response.setStatus(401);
}
  • 不返回 JSON,直接设置 401 状态码
  • 避免 WebSocket 连接收到非协议消息

2. WebService 绕过

SOAP WebService 请求绕过认证:

java
if (exchange.getRequest().getURI().getPath().contains("webservice")) {
    return chain.filter(exchange);
}

3. Swagger 开发模式

开发环境允许 Knife4j 绕过认证:

java
if (swaggerEnabled && originHeaders.contains("Knife4j")) {
    return chain.filter(exchange);
}

配置:

yaml
knife4j:
  gateway:
    enabled: true  # 开发环境设置为 true

生产环境必须关闭!

Redis 缓存结构

Token 缓存

  • Key: AUTH_TOKEN:{token}
  • Type: String(JSON 序列化的 UserSessionObj
  • TTL: 会话过期时间(默认 30 分钟)

权限码缓存

  • Key: AUTH_PERMISSION:{token}
  • Type: Set
  • Value: 权限码列表
  • TTL: 与 Token 一致

用户 Token 集合

  • Key: USERID_TO_ACCESS:{userId}
  • Type: Set
  • Value: 该用户的所有有效 Token

安全建议

1. 白名单管理

  • 最小化白名单范围
  • 定期审查白名单配置
  • 敏感接口不要加入白名单
  • 生产环境关闭 Swagger

2. Token 安全

  • 使用 HTTPS 传输
  • Token 使用随机 UUID
  • 设置合理的过期时间
  • 定期清理过期 Token

3. 权限转发

  • 每个路由只配置所需的前缀,避免传递过多权限码
  • 前缀与权限码模块对应,确保覆盖完整
  • 不配置前缀时传递全部权限码,适用于需要完整权限的服务

4. 监控审计

  • 记录所有认证失败
  • 监控异常访问模式
  • 定期审查权限配置
  • 追踪权限变更历史

总结

  • 统一的认证入口(GatewayAuthFilter
  • 权限码按路由前缀按需转发(PermissionForwardGatewayFilter
  • 后端 AOP 完成权限校验(PermissionAopAspect
  • 灵活的白名单与临时 Token 限制
  • 自动 Token / 权限码续期
  • 单体 / 微服务双模支持