<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-common</artifactId>
<version>5.1.10</version>
</dependency>
Vert.x 通用认证与授权 (vertx-auth-common)
该 Vert.x 基础安全组件定义了通用的身份认证(Authentication)与权限授权(Authorization)顶层接口,可供所有 Vert.x 应用调用,底层由多种差异化安全 Provider 提供实际支撑。
vertx-web 内部同样全面依赖 vertx-auth 来管理其 Web 认证与权限校验。
在项目构建配置中引入依赖:
-
Maven(在
pom.xml中):
-
Gradle(在
build.gradle文件中):
compile 'io.vertx:vertx-auth-common:5.1.10'
核心基础概念
身份认证 (Authentication):核验用户的真实身份(例如校验账号密码是否正确)。
权限授权 (Authorization):核验该用户是否被允许执行某项特定操作或访问受限资源。
为了兼顾各种差异化的权限模型并保持极高的架构灵活性,所有授权断言均围绕 Authorization 统一接口展开。
在某些业务中,授权可能代表具体操作权限(Permission),例如访问所有打印机或访问特定编号打印机的权限;而在其他场景下,授权可能体现为业务角色(Role,如 admin、manager 等)。系统提供了丰富的工厂类以支持各类授权模型:
-
RoleBasedAuthorization:基于角色的权限授权 -
PermissionBasedAuthorization:基于操作权限字符的授权 -
WildcardPermissionBasedAuthorization:支持通配符(如printer:*)的权限授权 -
AndAuthorization:逻辑“与”组合授权 -
OrAuthorization:逻辑“或”组合授权 -
NotAuthorization:逻辑“非”排他授权
这一组抽象涵盖了各类多维权限场景:
-
基于角色(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 提供了两套清晰的抽象:
-
KeyStoreOptions:抽象 JVM Keystore 格式(如 JKS、PKCS12) -
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);