认证与权限安全
你当前正在浏览历史版本 Vert.x 4.5.35 的文档。 访问本页面的 最新版本。

Vert.x 通用认证与授权 (vertx-auth-common)

该 Vert.x 基础安全组件定义了通用的身份认证(Authentication)与权限授权(Authorization)顶层接口,可供所有 Vert.x 应用调用,底层由多种差异化安全 Provider 提供实际支撑。

vertx-web 内部同样全面依赖 vertx-auth 来管理其 Web 认证与权限校验。

在项目构建配置中引入依赖:

  • Maven(在 pom.xml 中):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-auth-common</artifactId>
  <version>5.0.12</version>
</dependency>
  • Gradle(在 build.gradle 文件中):

compile 'io.vertx:vertx-auth-common:5.0.12'

核心基础概念

身份认证 (Authentication):核验用户的真实身份(例如校验账号密码是否正确)。

权限授权 (Authorization):核验该用户是否被允许执行某项特定操作或访问受限资源。

为了兼顾各种差异化的权限模型并保持极高的架构灵活性,所有授权断言均围绕 Authorization 统一接口展开。

在某些业务中,授权可能代表具体操作权限(Permission),例如访问所有打印机或访问特定编号打印机的权限;而在其他场景下,授权可能体现为业务角色(Role,如 admin、manager 等)。系统提供了丰富的工厂类以支持各类授权模型:

这一组抽象涵盖了各类多维权限场景:

  • 基于角色(Role-based)的模型

  • 基于操作权限(Permission-based)的模型

  • 组合逻辑运算(AND、OR、NOT)

  • 基于时间的访问控制(例如仅允许在每月末 5 天的 8:00 至 10:00 访问)

  • 基于上下文网络环境(例如仅允许特定 IP 段访问)

  • 基于自定义脚本或硬编码规则的业务断言

具体特定的 AuthorizationProvider 期望接收何种格式,请参阅该 Provider 的专门文档。

身份认证流程 (Authentication)

调用 authenticate 验证用户身份。

第一个入参是封装了凭证信息的 Credentials 对象。其具体结构取决于底层 Provider 实现;对于常规的用户名/密码登录,其承载数据形如:

{
  "username": "tim",
  "password": "mypassword"
}

对于基于 JWT 或 OAuth Bearer 令牌的实现,它则承载 Token 字符串。

认证过程完全是异步非阻塞的。异步结果中包含一个 User 实例,代表当前已成功通过认证的用户主体。

请注意:新创建的认证用户对象本身并不携带具体的权限授权数据。 之所以将认证与授权完全解耦,是因为它们是两个可以由不同底层服务独立处理的阶段。 例如:你可以使用 OAuth 2.0 完成终端用户的登录认证,同时使用 JWT 或 MongoDB 授权 Provider 从数据库中动态加载该用户具体的系统权限列表。

使用简单用户名/密码完成用户认证的示例:

Credentials authInfo =
  new UsernamePasswordCredentials("tim", "mypassword");

authProvider.authenticate(authInfo)
  .onSuccess(user -> {
    System.out.println("用户 " + user.principal() + " 已成功完成身份认证");
  })
  .onFailure(Throwable::printStackTrace);

权限授权判断 (Authorization)

一旦获取到 User 实例,即可调用 authorizations() 查询其当前挂载的权限集。 刚经过认证的用户实例起初不包含任何权限,可以通过 AuthorizationProvider 从底层数据源异步加载并注入权限。

通过 AuthorizationProvider 加载并判定权限示例:

authorizationProvider.getAuthorizations(user)
  .onSuccess(done -> {
    // 权限缓存加载完成,执行权限断言判定
    if (PermissionBasedAuthorization.create("printer1234").match(user)) {
      System.out.println("该用户拥有 printer1234 访问权限");
    } else {
      System.out.println("无权访问该资源");
    }
  });

基于角色的权限判定示例(使用 RoleBasedAuthorization):

if (RoleBasedAuthorization.create("admin").match(user)) {
  // 当前用户具备管理员角色
}

权限字符串的具体解析完全由底层 Provider 决定,Vert.x 顶层不做任何强制假设。

权限缓存与清除

User 对象内部持有一份已加载权限的列表,后续的相同断言校验会优先命中内存缓存,从而避免对底层 Provider 产生重复的网络或数据库 I/O 开销。 若需主动清除该用户的本地权限缓存,调用 clear 即可。

用户主体信息 (Principal) 与衍生属性 (Attributes)

调用 principal 可读取认证成功后返回的主体标识 JSON 数据。 principal 是创建 User 对象时的原始凭证凭据;而 attributes 则是后续业务或安全链路注入的派生元数据(例如用户手机号、额外标签等)。两者隔离是为了防止后续处理意外篡改或污染原始身份凭证。

系统提供了便捷方法同时检索两处数据源:

if (user.containsKey("sub")) {
  // 系统会优先在 attributes 中查找,若未找到则回退至 principal 中查找
  String sub = user.get("sub");
}

编写自定义认证或授权 Provider

如需编写自定义安全提供者,实现以下一个或两个接口即可:

User 工厂方法支持根据传入的 principal JSON 内容构建 User 对象,并可按需传入 attributes 字典附加以下安全生命周期元数据:

  • exp:过期时间戳(秒)

  • iat:签发时间戳(秒)

  • nbf:生效起始时间戳(Not Before,秒)

  • leeway:时钟漂移容差(秒,用于弥补分布式服务器间的微小时间不同步)

安全伪随机数生成器 (PRNG)

由于 Java 原生 SecureRandom 在从操作系统熵池采集熵源时可能会触发阻塞,Vert.x 专门对其进行了非阻塞异步封装,完全杜绝了阻塞 EventLoop 线程的风险。

该 PRNG 默认采用混合模式:加盐播种阶段阻塞,生成阶段高并发非阻塞。每隔 5 分钟自动补充 64 位全新熵源。你可以通过系统属性调优算法与参数:

  • io.vertx.ext.auth.prng.algorithm:例如 SHA1PRNG

  • io.vertx.ext.auth.prng.seed.interval:加盐周期,单位毫秒(例如 1000)

  • io.vertx.ext.auth.prng.seed.bits:新熵位数(例如 128)

在上下文中跨 Handler 共享 PRNG

由于系统熵池属于稀缺宝贵的系统资源,频繁创建销毁 PRNG 会造成严重损耗。推荐使用 VertxContextPRNG 在全局上下文中高效复用:

// 安全生成 32 位随机防伪字符串
String token = VertxContextPRNG.current(vertx).nextString(32);
// 生成高安全性密码学随机整数
int randomInt = VertxContextPRNG.current(vertx).nextInt();

密码学安全密钥管理

加载与解析各类安全证书和密钥是常见但繁琐的任务。vertx-auth-common 提供了两套清晰的抽象:

  1. KeyStoreOptions:抽象 JVM Keystore 格式(如 JKS、PKCS12)

  2. PubSecKeyOptions:抽象常见的 PEM 文本格式

加载本地 Keystore 示例:

KeyStoreOptions options = new KeyStoreOptions()
  .setPath("/path/to/keystore/file")
  .setType("pkcs12")
  .setPassword("keystore-password")
  .putPasswordProtection("key-alias", "alias-password");

加载 PEM 格式证书或私钥(需符合 PKCS8 标准):

PubSecKeyOptions options = new PubSecKeyOptions()
  .setAlgorithm("RS256")
  .setBuffer(
    vertx.fileSystem()
      .readFileBlocking("/path/to/pem/file")
      .toString());

JSON Web Keys (JWK)

JWK 是 OpenID Connect 与现代 JWT 架构的标准密钥表达格式,将公私钥序列化为结构化的 JSON 对象。各大知名 IdP(如 Google、Microsoft、Keycloak)均对外暴露 jwks_uri 供客户端自动拉取。

责任链式组合多重认证 Provider (ChainAuth)

当应用需要同时对接多个认证源(例如优先在本地配置中匹配,未命中则转入企业 LDAP 查询)时,可以使用 ChainAuth:

“或”关系责任链(任一通过即认证成功):

ChainAuth.any()
  .add(ldapAuthProvider)
  .add(propertiesAuthProvider);

“与”关系责任链(必须同时满足全部认证源方可放行):

ChainAuth.all()
  .add(ldapAuthProvider)
  .add(propertiesAuthProvider);