Skip to content

权限验证拦截器 🔐

功能介绍 💡

TIP

权限验证拦截器就像一个智能门禁系统,负责管理用户的会话(Session)和权限验证。支持Redis和JWT两种验证方式,可以灵活应对不同的应用场景。

核心功能

  • ✅ 会话管理(Redis/JWT)
  • ✅ 权限验证
  • ✅ 基于Session字段的扩展权限控制
  • ✅ 令牌转换
  • ✅ 双因子认证
  • ✅ 动态更新session
  • ✅ 断言配置(predicates)

详细配置说明 ⚙️

核心参数说明

参数名类型必填默认值说明
enabledbooleanfalse是否启用拦截器
rulesarray[object]-规则列表

权限配置详解 (rules)

基础配置

参数名类型必填默认值说明
typestring-存储类型:redis/jwt
urlsarray[string]-需要拦截的URL列表
authorization-namestringauthorization令牌参数名
business-keystringbearer业务标识(多系统区分用)
expireDuration30m过期时间(如:30m)
is-generate-temp-codebooleanfalse是否生成临时码
predicatesarray[string]-断言条件列表,用于更精确地匹配请求

Redis专用配置

参数名类型必填默认值说明
redis-auto-expirebooleanfalse是否自动延期

JWT专用配置

参数名类型必填默认值说明
jwt-secretstring-JWT密钥
is-cryptobooleanfalse是否启用加密
encrypt-typestringDES3加密算法类型,支持DES3SM4
crypto-keystring-数据加密密钥(DES3算法要求24位,SM4算法要求16位)
jwt-check-logoutbooleanfalse是否验证退出状态

权限控制配置

参数名类型必填默认值说明
security-enabled-admin-authoritybooleanfalse是否启用管理员权限(超级管理员跳过接口权限验证)
security-visitor-urlsarray[string]-游客可访问地址(无需登录)
security-login-urlsarray[string]-登录后即可访问地址(无需接口权限)
security-forbid-urlsarray[string]-禁止访问地址(直接返回404)
security-session-rulesarray[object]-基于Session字段值的扩展权限规则,详见下方说明

Session字段扩展权限规则配置 [security-session-rules]

当用户的 authorizeUrlList 中没有对应接口权限时,可通过此规则基于Session中的字段值进行补充授权,两者为 OR 关系,满足其一即可访问。

参数名类型必填默认值说明
urlsarray[string]-需要控制的URL列表,支持AntPath匹配
match-typestringand条件匹配方式:and(所有条件均需满足)/ or(满足一个即可)
conditionsarray[object]-字段条件列表

Session字段条件配置 [conditions]

参数名类型必填默认值说明
field-typestring-字段来源:extended(extendedMap)/ dataScope(dataScopeMap)/ field(顶层字段)
field-namestring-字段名称。field 类型支持:userIdnicknameisSystem
valuesarray[string]-允许的值列表,列表内为 OR 关系(满足其一即通过)

用户相关配置

参数名类型必填默认值说明
user-login-urlsarray[string]-登录接口地址
user-logout-urlsarray[string]-退出接口地址
user-get-info-urlsarray[string]-获取用户信息接口
user-get-session-urlsarray[string]-获取会话信息接口
user-login-typestring-用户登录类型,local 本地用户验证
accountsarray[string]-本地用户配置列表
session-update-enabledbooleanfalse是否启用session更新(仅redis模式生效)
session-update-intervalDuration10msession更新间隔时间,超过此时间自动更新session(如:10m)
session-update-urlstring-session更新的URI地址
session-update-timeoutDuration30ssession更新请求超时时间(如:30s)

双因子认证配置

参数名类型必填默认值说明
two-factor-auth-enabledbooleanfalse是否启用双因子认证
two-factor-auth-expireDuration30m双因子认证有效期(如:30m)
two-factor-login-urlsarray[string]-双因子认证登录URL列表
two-factor-auth-urlsarray[string]-双因子认证验证URL列表

本地用户配置[accounts]

参数名类型必填默认值说明
account-idstring-账户ID
account-nostring-账号
passwordstring-密码
nicknamestring-昵称
is-adminbooleanfalse是否管理员
authorize-urlsarray[string]-授权的URL列表
authorize-codesarray[string]-授权的权限码列表

配置示例 📋

Redis存储配置

yaml
# Redis存储配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: redis  # 使用Redis存储
          urls:  # 拦截的URL
            - /demo/**
          business-key: demo  # 业务标识
          expire: 1h  # 1小时过期
          redis-auto-expire: true  # 自动续期
          user-login-urls:  # 登录接口
            - /demo/login
          user-logout-urls:  # 退出接口
            - /demo/logout
          security-visitor-urls:  # 游客权限
            - /demo/captcha
            - /demo/login
          security-login-urls:  # 登录权限
            - /demo/getInfo

带断言的Redis存储配置

yaml
# 带断言的Redis存储配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: redis  # 使用Redis存储
          urls:  # 拦截的URL
            - /api/user/**
          predicates:  # 断言条件
            - Method=GET,POST  # 只匹配GET和POST方法
            - Header=Content-Type,application/json  # 只匹配JSON内容类型
            - Query=version,v2  # 只匹配version=v2的请求
          business-key: user-api  # 业务标识
          expire: 2h  # 2小时过期
          redis-auto-expire: true  # 自动续期
          user-login-urls:
            - /api/user/login
          user-logout-urls:
            - /api/user/logout

本地用户验证配置

不需要调用微服务接口,直接配置一个用户

yaml
# 本地用户验证配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: redis  # 使用Redis存储
          urls:  # 拦截的URL
            - /demo/**
          business-key: demo  # 业务标识
          expire: 1h  # 1小时过期
          redis-auto-expire: true  # 自动续期
          user-login-urls:  # 登录接口
            - /demo/login
          user-login-type: local
          accounts:
            - account-no: admin
              account-id: 1
              password: 1234567
              nickname: 管理员11
              is-admin: true
              authorize-urls:
                - /admin/**
              authorize-codes:
                - adddd
          user-logout-urls:  # 退出接口
            - /demo/logout
          security-visitor-urls:  # 游客权限
            - /demo/captcha
            - /demo/login
          security-login-urls:  # 登录权限
            - /demo/getInfo

带断言的JWT存储配置

yaml
# 带断言的JWT存储配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: jwt
          urls: 
            - /api/admin/**
          predicates:  # 断言条件
            - Method=POST,PUT,DELETE  # 只匹配修改类请求
            - Header=X-Admin-Mode,true  # 只匹配管理员模式
          jwt-secret: "a748d0d6-1b30-4489-9751-1115134dcb8a"
          is-crypto: true
          crypto-key: "a748d0d6a748d0d6a748d0d6"
          jwt-check-logout: true

JWT存储配置

yaml
# JWT存储配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: jwt
          urls: 
            - /demo/**
          jwt-secret: "a748d0d6-1b30-4489-9751-1115134dcb8a"
          is-crypto: true
          crypto-key: "a748d0d6a748d0d6a748d0d6"
          jwt-check-logout: true

Session字段扩展权限配置

适用于某些接口不在用户 authorizeUrlList 中,但可以通过 Session 里的字段值来判断是否有权访问的场景。

示例1:按角色控制(AND条件)

要求用户的 extendedMap.roleadminsuperAdmin isSystemtrue,才能访问 /demo/admin/**

yaml
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: redis
          urls:
            - /demo/**
          security-session-rules:
            - urls:
                - /demo/admin/**
              match-type: and          # 所有条件均需满足
              conditions:
                - field-type: extended # 从 extendedMap 取值
                  field-name: role
                  values:              # 满足其中一个即可
                    - admin
                    - superAdmin
                - field-type: field    # 从顶层字段取值
                  field-name: isSystem
                  values:
                    - "true"

示例2:按角色控制(OR条件)

满足 extendedMap.role == admin isSystem == true,任意一个条件即可访问。

yaml
security-session-rules:
  - urls:
      - /demo/admin/**
    match-type: or             # 满足一个条件即可
    conditions:
      - field-type: extended
        field-name: role
        values:
          - admin
      - field-type: field
        field-name: isSystem
        values:
          - "true"

示例3:多组规则

不同 URL 使用不同的 Session 字段规则。

yaml
security-session-rules:
  - urls:
      - /demo/admin/**
    match-type: and
    conditions:
      - field-type: extended
        field-name: role
        values:
          - admin
          - superAdmin
  - urls:
      - /demo/report/**
    match-type: or
    conditions:
      - field-type: extended
        field-name: department
        values:
          - finance
          - audit
      - field-type: dataScope
        field-name: reportScope
        values:
          - all

权限判断逻辑说明

用户请求某个 URL

是否在 security-visitor-urls? → 是 → 直接放行
    ↓ 否
用户是否已登录? → 否 → 返回 401
    ↓ 是
是否在 security-login-urls? → 是 → 直接放行
    ↓ 否
是否超级管理员且启用了 security-enabled-admin-authority? → 是 → 直接放行
    ↓ 否
URL 是否在 authorizeUrlList 中? → 是 → 放行
    ↓ 否
是否匹配 security-session-rules 且条件通过? → 是 → 放行
    ↓ 否
返回 403

断言配置

断言用于判断请求是否匹配当前路由,支持多种匹配方式。合理配置断言可以实现精确的路由控制和灵活的请求分发策略。 :::

📋 断言类型速查表

断言类型核心功能配置格式典型示例使用场景
Method 🔧HTTP方法匹配Method=方法1,方法2,...Method=GET,POST,PUTRESTful接口、操作权限控制
Header 📋请求头匹配(支持正则)Header=名称,正则表达式Header=X-Id,\d+认证鉴权、版本控制
Query查询参数匹配Query=参数名,正则表达式Query=name,test参数校验、条件过滤
Host 🌐主机名匹配(支持正则)Host=主机名1,主机名2,...Host=demo\.wueasy\.com多域名部署、环境隔离

🎯 断言配置最佳实践

🔐 1. 请求方法控制(Method)

yaml
# RESTful接口设计
Method=GET                # 查询操作
Method=POST               # 创建操作
Method=PUT,PATCH          # 更新操作
Method=DELETE             # 删除操作

# 权限控制组合
Method=GET                # 只读接口
Method=POST,PUT,DELETE    # 写操作接口

📊 2. 请求头匹配(Header)- 认证利器

yaml
# API版本控制
Header=X-API-Version,v1\..*    # v1.x版本
Header=X-API-Version,v2\.\d+   # v2.数字版本

# 认证令牌验证
Header=Authorization,Bearer\s.+ # Bearer Token格式
Header=X-API-Key,\w{32}         # 32位API密钥

# 客户端类型识别
Header=User-Agent,.*Mobile.*    # 移动端请求
Header=Content-Type,application/json # JSON格式请求

🔍 3. 查询参数匹配(Query)

yaml
# 参数存在性检查
Query=debug                    # 只要存在debug参数就匹配
Query=debug,true               # debug参数值必须为true
Query=version,v\d+\.\d+        # version参数格式验证

# 多参数组合
Query=page,\d+                # 分页参数必须是数字
Query=sort,(asc|desc)         # 排序参数只能是asc或desc

🌐 4. 主机名匹配(Host)- 多环境部署

yaml
# 生产环境
Host=api\.wueasy\.com          # 精确匹配

# 测试环境(支持子域名)
Host=.*\.test\.wueasy\.com      # 匹配所有测试子域名
Host=^([a-z]+)\.test\.wueasy\.com$ # 命名捕获组匹配

# 多域名支持
Host=api\.wueasy\.com,api\.wueasy\.cn # 国内外域名

登录接口开发 💻

返回对象要求

java
@PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResultVo<SessionVo>> login(@RequestBody @Valid LoginDto dto) {
    return Mono.just(ResultVo.ok(loginService.login(dto)));
}

登录逻辑示例

java
@Override
public SessionVo login(LoginDto dto) {
    // 1. 验证登录信息
    if (!"admin".equals(dto.getAccountNo()) || !"123456".equals(dto.getPassword())) {
        throw new InvokeException(-1, "用户名或密码不正确");
    }

    // 2. 创建会话信息
    SessionVo session = new SessionVo();
    session.setIsSystem(true);  // 是否管理员
    session.setUserId("1");
    session.setNickname("wueasy");

    // 3. 设置权限信息
    Set<String> authorizeUrls = new HashSet<>();
    authorizeUrls.add("/api/admin/**");
    session.setAuthorizeUrlList(authorizeUrls);

    return session;
}

动态更新Session说明 🔄

功能介绍

动态更新Session功能允许在用户会话期间实时更新用户的权限信息,无需重新登录。这在以下场景中特别有用:

  • 用户权限发生变更时
  • 用户角色调整时
  • 数据权限范围变化时
  • 需要实时同步最新权限状态时

配置要求

要启用动态更新Session功能,需要在配置中添加以下参数:

yaml
# 动态更新Session配置示例
gateway:
  filter:
    session:
      enabled: true
      rules:
        - type: redis  # 仅Redis模式支持动态更新
          urls:
            - /demo/**
          business-key: demo
          expire: 1h
          redis-auto-expire: true
          # 动态更新Session配置
          session-update-enabled: true  # 启用session更新
          session-update-interval: 10m  # 更新间隔:10分钟
          session-update-url: "http://localhost:8082/getLastSession"  # 更新接口地址
          session-update-timeout: 30s  # 请求超时时间
          user-login-urls:
            - /demo/login

接口开发

1. Controller层实现

java
/**
 * 获取最新的Session信息
 * 注意:此接口会被网关自动调用,用于更新用户会话信息
 */
@PostMapping(value = "/getLastSession", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResultVo<SessionVo>> getLastSession() {
    return Mono.just(ResultVo.ok(loginService.getLastSession()));
}

2. Service层实现

java
@Override
public SessionVo getLastSession() {
    // 创建最新的会话信息
    SessionVo session = new SessionVo();
    session.setIsSystem(false);  // 是否超级管理员
    session.setUserId("1");      // 用户ID
    session.setNickname("wueasy234");  // 用户昵称
    
    // 设置权限代码集合(用于前端权限判断)
    Set<String> authorizeCodeList = new HashSet<String>();
    authorizeCodeList.add("user");
    authorizeCodeList.add("admin");  // 可以动态添加新权限
    session.setAuthorizeCodeList(authorizeCodeList);

    // 设置可访问的URL地址权限
    Set<String> linkUrlSetAll = new HashSet<String>();
    linkUrlSetAll.add("/demo/get");
    linkUrlSetAll.add("/demo/list");  // 可以动态添加新的URL权限
    session.setAuthorizeUrlList(linkUrlSetAll);
    
    // 设置数据权限映射(重要:用于数据权限验证拦截器)
    Map<String, Set<String>> dataAuthorizeMap = new HashMap<>();
    Set<String> demoDataSet = new HashSet<>();
    demoDataSet.add("测试1");
    demoDataSet.add("测试2");
    demoDataSet.add("测试3");  // 可以动态添加新的数据权限
    dataAuthorizeMap.put("demo", demoDataSet);
    
    // 可以添加多个业务的数据权限
    Set<String> userDataSet = new HashSet<>();
    userDataSet.add("org001");
    userDataSet.add("org002");
    dataAuthorizeMap.put("USER_DATA", userDataSet);
    
    session.setDataAuthorizeMap(dataAuthorizeMap);
    
    // 设置更新字段列表(指定本次更新哪些字段)
    List<String> updateFieldList = new ArrayList<>();
    updateFieldList.add("nickname");           // 更新昵称
    updateFieldList.add("authorizeCodeList");  // 更新权限代码
    updateFieldList.add("authorizeUrlList");   // 更新URL权限
    updateFieldList.add("dataAuthorizeMap");   // 更新数据权限
    session.setUpdateFieldList(updateFieldList);
    
    return session;
}

工作原理

  1. 触发条件:当用户访问受保护的URL时,网关会检查Session的更新时间
  2. 时间判断:如果距离上次更新超过了配置的session-update-interval时间
  3. 自动调用:网关会自动调用配置的session-update-url接口
  4. 更新Session:获取最新的Session信息并更新到Redis中
  5. 继续处理:使用更新后的Session信息继续处理当前请求

SessionVo对象结构说明

java
public class SessionVo {
    private String userId;                           // 用户ID
    private String nickname;                         // 用户昵称
    private Boolean isSystem;                        // 是否超级管理员
    private Set<String> authorizeCodeList;           // 权限代码集合
    private Set<String> authorizeUrlList;            // 可访问URL集合
    private Map<String, Set<String>> dataAuthorizeMap; // 数据权限映射
    
    /**
     * 更新字段集合,只有更新session时使用
     * 用于指定本次更新需要更新哪些字段,未指定的字段将保持原值
     */
    private List<String> updateFieldList;
    
    // 其他字段...
}

数据权限映射说明

dataAuthorizeMap 是一个重要的字段,用于数据权限验证拦截器:

java
Map<String, Set<String>> dataAuthorizeMap = new HashMap<>();

// 业务代码 -> 权限值集合
dataAuthorizeMap.put("USER_DATA", Set.of("org001", "org002"));     // 用户数据权限
dataAuthorizeMap.put("ORDER_DATA", Set.of("merchant001"));         // 订单数据权限
dataAuthorizeMap.put("DEPT_DATA", Set.of("dept001", "dept002"));   // 部门数据权限

updateFieldList字段详解

updateFieldList 字段用于精确控制Session更新的范围,提高更新效率并避免不必要的数据覆盖。

支持的字段名称

字段名说明示例
nickname用户昵称用户修改昵称时
avatarUrl头像地址用户修改头像时
isSystem超级管理员标识用户角色变更时
authorizeCodeList权限代码集合权限变更时
authorizeUrlListURL权限集合URL访问权限变更时
dataAuthorizeMap数据权限映射数据权限范围变更时
extendedMap拓展map扩展信息变更时

使用原则

  1. 指定更新字段:只有在updateFieldList中指定的字段才会被更新
  2. 保持原值:未指定的字段将保持Session中的原有值
  3. 提高效率:避免全量更新,减少不必要的数据传输
  4. 防止覆盖:避免意外覆盖不需要更新的字段

使用场景示例

1. 仅更新用户昵称

java
// 用户修改昵称时,只更新昵称字段
public SessionVo getLastSession() {
    String userId = getCurrentUserId();
    User user = userService.getById(userId);
    
    SessionVo session = new SessionVo();
    session.setNickname(user.getNickname());  // 设置新昵称
    
    // 只更新昵称字段
    List<String> updateFieldList = Arrays.asList("nickname");
    session.setUpdateFieldList(updateFieldList);
    
    return session;
}

2. 仅更新数据权限

java
// 用户的数据权限范围发生变化时,只更新数据权限
public SessionVo getLastSession() {
    String userId = getCurrentUserId();
    
    // 获取用户最新的数据权限
    Map<String, Set<String>> dataAuthorizeMap = new HashMap<>();
    
    // 获取组织权限
    Set<String> orgIds = dataPermissionService.getUserOrgPermissions(userId);
    dataAuthorizeMap.put("USER_DATA", orgIds);
    
    // 获取商户权限
    Set<String> merchantIds = dataPermissionService.getUserMerchantPermissions(userId);
    dataAuthorizeMap.put("ORDER_DATA", merchantIds);
    
    SessionVo session = new SessionVo();
    session.setDataAuthorizeMap(dataAuthorizeMap);
    
    // 只更新数据权限字段
    List<String> updateFieldList = Arrays.asList("dataAuthorizeMap");
    session.setUpdateFieldList(updateFieldList);
    
    return session;
}

3. 批量更新多个字段

java
// 用户角色发生重大变更时,更新多个相关字段
public SessionVo getLastSession() {
    String userId = getCurrentUserId();
    
    // 从数据库获取用户最新信息
    User user = userService.getById(userId);
    List<Role> roles = roleService.getByUserId(userId);
    List<Permission> permissions = permissionService.getByUserId(userId);
    
    SessionVo session = new SessionVo();
    session.setIsSystem(user.getIsAdmin());
    session.setAuthorizeCodeList(buildAuthorizeCodes(roles));
    session.setAuthorizeUrlList(buildAuthorizeUrls(permissions));
    session.setDataAuthorizeMap(buildDataAuthorizeMap(userId));
    
    // 更新多个字段
    List<String> updateFieldList = Arrays.asList(
        "isSystem", 
        "authorizeCodeList", 
        "authorizeUrlList", 
        "dataAuthorizeMap"
    );
    session.setUpdateFieldList(updateFieldList);
    
    return session;
}

注意事项

  1. 仅Redis模式支持:动态更新Session功能仅在Redis存储模式下有效
  2. 性能考虑:不要设置过短的更新间隔,避免频繁调用更新接口
  3. 接口安全:更新接口应该有适当的安全验证
  4. 错误处理:更新失败时会继续使用原有Session信息
  5. 数据一致性:确保更新接口返回的数据与实际权限状态一致

令牌使用说明 🎫

请求示例

shell
# 使用curl发送请求
curl -X GET \
  -H "authorization: your-token-here" \
  http://127.0.0.1:8080/demo/getInfo

最佳实践 💡

  1. 存储方式选择

    • Redis:适合需要频繁更新的场景
    • JWT:适合分布式系统,无状态管理
  2. 安全建议

    • 使用HTTPS传输
    • 设置合理的过期时间
    • 定期更换密钥
  3. 性能优化

    • 合理设置缓存策略
    • 避免频繁的权限检查
    • 优化会话存储结构

常见问题 ❓

  1. 会话失效

    • 检查过期时间设置
    • 验证续期配置
    • 确认存储服务状态
  2. 权限验证失败

    • 检查权限配置
    • 验证令牌格式
    • 确认用户权限列表
  3. security-session-rules 不生效

    • 确认 URL 已被 urls 拦截范围覆盖
    • 确认用户已登录(sessionVo 不为空)
    • 检查 field-typefield-name 是否与登录时写入 Session 的字段一致
    • field-type: fieldisSystem 值为字符串 "true" / "false",注意引号

安全提示 ⚠️

CAUTION

  1. 妥善保管JWT密钥和加密密钥
  2. 定期清理过期会话
  3. 监控异常登录行为
  4. 及时更新权限配置

相关文档 📚