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

SQL 客户端身份认证与授权提供者 (vertx-auth-sql-client)

该组件基于 Vert.x 响应式 SqlClient 提供了 AuthenticationProvider 认证与 AuthorizationProvider 授权实现,适用于任何兼容 SQL 标准的关系型数据库(如 PostgreSQL、MySQL、MariaDB、Oracle、DB2 等)。

要在项目中使用该组件,请在构建描述文件的 dependencies 区域添加以下依赖:

  • Maven(在 pom.xml 中):

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

compile 'io.vertx:vertx-auth-sql-client:5.0.12'

创建提供者前首先需要准备一个 SqlClient 实例。有关如何创建与配置 SQL 客户端,请参阅对应数据库驱动的文档。

就绪后,按如下方式创建 SqlAuthentication 实例:

SqlAuthenticationOptions options = new SqlAuthenticationOptions();
// SQL client 可以是任意主流关系型数据库实现:
// *. PostgreSQL
// *. MySQL
// *. 等等……
AuthenticationProvider authenticationProvider =
  SqlAuthentication.create(sqlClient, options);

获得实例后,即可像使用常规 AuthenticationProvider 一样进行身份认证与授权。

开箱即用的默认配置预设了标准的 SQL 查询模板,若你的数据库使用了自定义的 Schema,可以通过以下方法轻松覆盖默认 SQL: setAuthenticationQuery、 setPermissionsQuery 以及 setRolesQuery。

推荐的基础表结构(DDL)定义如下所示:

--
-- 请根据具体关系型数据库方言对脚本进行微调
--
CREATE TABLE users (
  username VARCHAR(255) NOT NULL,
  password VARCHAR(255) NOT NULL
);

CREATE TABLE users_roles (
  username VARCHAR(255) NOT NULL,
  role VARCHAR(255) NOT NULL
);

CREATE TABLE roles_perms (
  role VARCHAR(255) NOT NULL,
  perm VARCHAR(255) NOT NULL
);

ALTER TABLE users ADD CONSTRAINT pk_username PRIMARY KEY (username);
ALTER TABLE users_roles ADD CONSTRAINT pk_users_roles PRIMARY KEY (username, role);
ALTER TABLE roles_perms ADD CONSTRAINT pk_roles_perms PRIMARY KEY (role, perm);

ALTER TABLE users_roles ADD CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users(username);

密码哈希策略

本提供者遵循 PHC 字符串格式规范 (PHC String Format Spec) 对密码进行安全哈希。

若你正在迁移既有的遗留系统,直接切换哈希策略将导致现存旧密码校验失败(PHC 新格式本身自带算法标识,未来升级不会受此影响)。升级方案是要求用户重置密码,或者在首次成功登录后以新策略重新哈希并回写记录。
强烈建议在数据库中永远存储密码的哈希摘要,且必须配合密码 Salt 盐值生成;强哈希算法(如 PBKDF2、Argon2、Bcrypt)是防范撞库与彩虹表的基石。严禁以明文形式存储任何用户密码!

数据隐私合规说明 (GDPR)

通用数据保护条例(GDPR)是欧盟的一项通用法律规范。它高于各成员国的本土数据保护法,并对既有规范进行了扩展。本节绝非条例的详尽法律解释,而是简要说明本组件如何遵循合规要求。企业若违反相关规定,最高可能面临年营业额 4% 或 2000 万欧元的巨额罚款。因此,Vert.x 致力于确保开发者在使用认证组件时具备合规基础。

法律核心术语定义:

  • 数据主体(Data Subject):个人数据所关联的自然人(如终端用户)

  • 个人数据(Personal Data):与已识别或可识别自然人相关的任何信息

  • 数据处理(Data Processing):对个人数据执行的任何操作(无论人工或自动化)

  • 控制者(Controller):决定数据处理目的与方式的实体(即企业组织)

  • 处理者(Processors):代表控制者处理数据的实体(如云计算服务商)

GDPR 核心权利与要求包括:

  • “被遗忘权”——擦除权(Right to erasure)

  • 限制处理权——将用户档案标记为限制处理(Right to restriction of processing)

  • 数据可携权——导出个人数据(Right to portability)

  • 更正权——允许用户修改个人资料(Right to rectification)

  • 知情与访问权——查看个人所有数据(Right to access)

  • 显式同意复选框

  • 年龄限制核验

  • 数据最小化原则——及时销毁无用数据

本认证模块在设计上不直接存储任何属于数据主体的可识别个人数据,因而天然满足 GDPR 规范。数据库记录中唯一的标识是用户名(username),它本身并不强制与任何现实个人信息绑定。

要在应用中管理用户的个人数据,你应该建立独立的业务模型,并将用户名列作为外键关联至你的业务数据。建议在业务表中维护一个布尔标记字段,用于在用户行使“限制处理权”时将该个人数据标记为限制状态;这意味着在标记为 true 时,系统不得对其执行非必要处理(如批量发送营销邮件)。

“擦除权”并不代表必须抹去系统内的所有业务记录。例如在银行系统中,用户行使被遗忘权不能用于抹消未偿还的贷款或债务。你被允许保留业务记录,但必须抹除所有可识别的个人隐私数据。在使用 Vert.x Auth SQL 时,你可以清理业务资料表,只要确保保留的用户名无法反向关联至自然人身份即可。

请特别注意:合规性在数据备份与归档中同样必须得到保证!建议将业务归档与擦除操作记录分别独立存档,以便能够单独审计与重放。

密码哈希生成流程

在实际应用开发中,必然包含新用户注册或密码重置流程。 如前所述,密码不能以明文存储,而必须按照设定的哈希策略进行计算。将新密码安全存储至数据库包含 3 个标准步骤:

  1. 生成安全随机盐值字符串(Salt)

  2. 基于盐值与密码明文计算哈希摘要

  3. 将用户名与生成的哈希摘要写入数据库

String hash = sqlAuth.hash(
  "pbkdf2", // 哈希算法(OWASP 官方推荐)
  VertxContextPRNG.current().nextString(32), // 安全伪随机盐值
  "sausages" // 原始密码明文
);

// 保存至数据库:
sqlClient
  .preparedQuery("INSERT INTO users (username, password) VALUES ($1, $2)")
  .execute(Tuple.of("tim", hash))
  .onSuccess(rowset -> {
    // 密码成功更新入库
  });

执行身份验证

使用该实现执行身份验证时,认证信息中必须包含 username 与 password 字段:

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

authProvider.authenticate(authInfo)
  .onSuccess(user -> System.out.println("用户:" + user.principal()))
  .onFailure(err -> {
    // 认证失败!
  });

权限授权:用户-角色-权限模型 (RBAC)

尽管 Vert.x Auth 本身并不强制固定权限模型(权限在底层仅被视作透明字符串),但该实现采用了标准且成熟的“用户 / 角色 / 权限”体系:一个用户可以拥有零个或多个角色,一个角色可以包含零个或多个具体权限。

若需核验用户是否拥有某项特定权限,只需按如下方式对用户进行断言匹配:

sqlAuthZ.getAuthorizations(user)
  .onSuccess(v -> {
    if (PermissionBasedAuthorization.create("commit_code").match(user)) {
      // 匹配成功,拥有权限!
    }
  });