网关认证与权限转发
Molan Cloud 在网关层实现了认证和权限转发机制,权限校验由后端服务通过 AOP 完成。
架构设计
过滤器链
网关使用两个核心过滤器:
GatewayAuthFilter - 认证过滤器(GlobalFilter,Order=1)
- 验证 Token 有效性
- 解析用户信息
- 白名单检查
- 获取用户权限码
- Token 续期
PermissionForwardGatewayFilter - 权限转发过滤器(Per-route GatewayFilter)
- 从 exchange attribute 获取用户权限码
- 按前缀过滤当前服务所需的权限码
- 通过 Header 转发给后端服务
处理流程
请求进入
↓
GatewayAuthFilter(认证 + 加载权限)
↓ Token 验证
↓ 白名单检查
↓ 用户信息解析
↓ 获取用户权限码 → 存入 exchange attribute
↓ 自动续期
↓
PermissionForwardGatewayFilter(权限转发)
↓ 读取 exchange attribute 中的权限码
↓ 按前缀过滤
↓ 写入 USER_PERMISSIONS Header
↓
后端服务
↓ PermissionAopAspect(AOP 权限校验)认证过滤器
核心功能
GatewayAuthFilter 负责验证用户身份并加载权限信息。
处理逻辑:
WebService 绕过
javaif (exchange.getRequest().getURI().getPath().contains("webservice")) { return chain.filter(exchange); }Swagger 开发模式
javaif (swaggerEnabled && originHeaders.contains("Knife4j")) { return chain.filter(exchange); }无 Token 请求
- 白名单路径:放行
- 非白名单路径:返回未登录
Token 验证
- 从 Redis 获取 Token 信息(
AUTH_TOKEN:{token}) - 检查 Token 是否存在
- 检查是否被踢出(
kickout=1)
- 从 Redis 获取 Token 信息(
获取权限码
javaSet<String> permissions = reactiveRedisTemplate.opsForSet() .members(AuthConst.AUTH_PERMISSION_CACHE_PREFIX + token) .collect(Collectors.toSet()); // 存入 exchange attribute,供 PermissionForwardGatewayFilter 使用 newExchange.getAttributes().put(ATTR_USER_PERMISSIONS, permissions);自动续期
javaif (expireInSeconds > 0 && expireInSeconds < effectiveSeconds / 2) { reactiveRedisTemplate.expire(AUTH_TOKEN:{token}, effectiveSeconds); reactiveRedisTemplate.expire(AUTH_PERMISSION:{token}, effectiveSeconds); }用户信息传递
javaString encodeUser = URLEncoder.encode(userJson, StandardCharsets.UTF_8); newRequest.header(AuthConst.HEADER_USER_INFO, encodeUser);
白名单配置
配置类: GatewayAuthProperties
配置前缀: molandev.security
内部白名单(innerWhiteUris):
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):
molandev:
security:
white-uris:
- /your/custom/path合并规则: 自定义白名单与内部白名单合并,使用 AntPathMatcher 匹配。
匹配规则:
- 使用
AntPathMatcher进行路径匹配 - 支持通配符:
*和** /sys/user/*- 匹配一级路径/sys/user/**- 匹配所有子路径
临时 Token 限制
临时 Token 只能访问特定接口:
配置:
molandev:
security:
temp-uris:
- /sys/personal/updatePassword # 修改密码
- /sys/personal/info # 个人信息检查逻辑:
if (UserSessionObj.TEMP_FLAG.equals(userSessionObj.getTemp())) {
if (checkTempUri(path)) {
return chain.filter(newExchange);
} else {
return handleUnauthorized(exchange);
}
}权限转发过滤器
核心功能
PermissionForwardGatewayFilter 将用户权限码按前缀过滤后,通过 Header 转发给后端服务。
网关只做认证与权限转发;权限校验由后端 PermissionAopAspect 根据 @HasPermission 注解完成。
处理逻辑:
获取权限码
javaSet<String> userPermissions = exchange.getAttribute(GatewayAuthFilter.ATTR_USER_PERMISSIONS);按前缀过滤
javaString filtered = permissions.stream() .filter(permission -> { for (String prefix : permissionPrefixes) { if (permission.startsWith(prefix)) { return true; } } return false; }) .collect(Collectors.joining(","));写入 Header(明文逗号分隔,与后端
PermissionAopAspect解析方式一致)javaServerHttpRequest 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 路由配置示例:
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表示只传递以sys、task、msg开头的权限码- 不配置前缀时,传递所有权限码
- 前缀与权限码格式
模块:功能:操作中的模块部分对应
请求头传递
网关将用户信息和权限码通过请求头传递给后端服务:
传递内容:
// 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 | 用户 Token | uuid-string |
USER_INFO | URL 编码的用户信息 JSON | %7B%22id%22%3A...%7D |
USER_PERMISSIONS | 明文权限码(逗号分隔) | sys:user:add,sys:user:edit |
后端获取:
// 获取当前用户(AuthUtils 内部会对 USER_INFO 做 URLDecoder)
ContextUser user = AuthUtils.getUser();
// 获取权限码(由 PermissionAopAspect 内部处理,业务代码无需关心)安全过滤器
RemoveHeaderGlobalFilter
全局过滤器,在认证之前剥离客户端伪造的内部鉴权头,必须早于 GatewayAuthFilter 执行。
剥离列表:
| Header | 说明 |
|---|---|
INNER | 内部调用标记 |
USER_INFO | 用户上下文(仅允许网关写入) |
USER_PERMISSIONS | 权限码(仅允许网关写入) |
网关在认证通过后通过 headers.set(...)(先 remove 再 set)写入真实值。业务服务不得对公网暴露;敏感头只信任网关注入结果。
// 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 获取数据
- 不负责权限校验,仅做认证
核心逻辑:
@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:
配置项:
molandev:
security:
mode: LOCAL # LOCAL 或 GATEWAY
check-permission: true # 是否启用权限校验,默认 trueLOCAL 模式:
- 注册
LocalAuthFilter(认证) - 注册
PermissionAopAspect(权限校验,从 Redis 读取权限码)
GATEWAY 模式:
- 不注册
LocalAuthFilter(认证由网关完成) - 注册
PermissionAopAspect(权限校验,从 Header 读取权限码)
特殊处理
1. WebSocket 支持
WebSocket 连接在单体模式下返回 401 状态码:
if (isWebSocket) {
response.setStatus(401);
}- 不返回 JSON,直接设置 401 状态码
- 避免 WebSocket 连接收到非协议消息
2. WebService 绕过
SOAP WebService 请求绕过认证:
if (exchange.getRequest().getURI().getPath().contains("webservice")) {
return chain.filter(exchange);
}3. Swagger 开发模式
开发环境允许 Knife4j 绕过认证:
if (swaggerEnabled && originHeaders.contains("Knife4j")) {
return chain.filter(exchange);
}配置:
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 / 权限码续期
- 单体 / 微服务双模支持