
本指南将向您展示如何构建并保护一个简单的 符合 FIDO2 标准(通常称为 Web Authentication API)的 vert.x Web 应用程序。Web Authentication API(也称为 WebAuthn)是由 W3C 和 FIDO 联盟在 Google、Mozilla、Microsoft、Yubico 和其他公司的参与下编写的规范。该 API 允许服务器使用公钥加密而不是密码来注册和认证用户。
通过本指南,您将拥有一个无需密码即可执行身份验证的运行中的应用程序。这将通过 WebAuthn 实现,WebAuthn 是一个新的 W3C 全球标准,用于 Web 上的安全身份验证,并受所有主流浏览器和平台支持。
文本编辑器或 IDE
Java 8 或更高版本(建议使用 11 或 >=15 获取额外的安全算法)
互联网连接
Web Authentication API 是一个安全的 API,供应商决定遵循最佳实践。虽然您可以在不使用 SSL 的情况下构建服务器,但现代 Web 浏览器不会连接或允许将 WebAuthn API 用于不使用 SSL 的服务器,即使是在开发过程中也是如此。
在我们开始使用 WebAuthn 之前,我们需要确保即使是我们的开发应用程序也已启用 SSL。为此,我们需要创建一个有效的 SSL 证书。请注意,自签名证书仍然允许使用
要为您的 IP 地址创建自签名证书,请执行以下操作:
export IP=10.0.0.2
export CERTSTORE_SECRET=password # (1)
keytool \
-genkeypair \
-alias rsakey \
-keyalg rsa \
-storepass ${CERTSTORE_SECRET} \
-keystore certstore.jks \
-storetype JKS \
-dname "CN=${IP}.nip.io,O=Vert.x Development" # (2)
不要使用此密码!
将 CN 替换为您自己的 IP 地址(localhost 除外),后缀为 .nip.io
对于此设置,我们依赖于一个免费的 DNS 服务器,它在查询时返回您的 IP 地址。Web 上也存在其他提供相同结果的服务,例如:
目前我们有了一个 SSL 证书,但对于现代 Java 版本而言,其格式被认为是过时的,因此我们需要第二步将其转换为 PKCS#12
keytool \
-importkeystore \
-srckeystore certstore.jks \
-destkeystore certstore.jks \
-deststoretype pkcs12
您的新 SSL 证书位于 certstore.jks
文件中。
在能够登录您的应用程序之前,我们需要注册一个 FIDO2 身份验证器。此过程类似于 Web 应用程序中的“注册”流程。然而,此图试图说明一些差异
用户仅使用用户名注册
您的服务器(依赖方)创建安全挑战
浏览器将此信息传递给令牌设备
令牌为此信息生成新的密钥对
挑战被签名并返回给服务器(RP)
服务器验证挑战是否正确并存储公钥
用户仅使用用户名进行身份验证
您的服务器(依赖方)创建安全挑战
浏览器将此信息传递给令牌设备
令牌生成验证凭据并签署挑战
浏览器创建身份验证断言
服务器验证断言是否正确并允许用户访问
您可以在此处找到完整的源代码,现在我们只介绍重要部分。
为了实现注册和身份验证功能,我们需要能够存储和查询身份验证器数据。为此,我们需要提供一些精确执行此操作的函数。您可以在此处查看其源代码。在您的 Verticle 中,您首先创建此对象,如下所示:
// Dummy database, real world workloads
// use a persistent store or course!
InMemoryStore database = new InMemoryStore();
为了使用 FIDO2,我们需要配置身份验证提供者如何工作。为此,我们需要创建并配置一个 WebAuthn
对象
// create the webauthn security object
WebAuthn webAuthN = WebAuthn.create(
vertx,
new WebAuthnOptions() // (1)
.setRelyingParty(new RelyingParty()
.setName("Vert.x FIDO2/webauthn Howto"))
.setUserVerification(UserVerification.DISCOURAGED) // (2)
.setAttestation(Attestation.NONE) // (3)
.setRequireResidentKey(false) // (4)
.setChallengeLength(64) // (5)
.addPubKeyCredParam(PublicKeyCredential.ES256) // (6)
.addPubKeyCredParam(PublicKeyCredential.RS256)
.addTransport(AuthenticatorTransport.USB) // (7)
.addTransport(AuthenticatorTransport.NFC)
.addTransport(AuthenticatorTransport.BLE)
.addTransport(AuthenticatorTransport.INTERNAL))
// where to load/update authenticators data
.authenticatorFetcher(database::fetcher)
.authenticatorUpdater(database::updater);
所有配置都在 WebAuthnOptions
中进行。这只是一个合理默认值的小例子,有关更多选项,请查阅 javadoc。
验证用户时,我们并不真正要求他们被验证。
在注册过程中,我们不希望验证硬件。
我们不需要常驻密钥来让用户进行身份验证。
定义挑战的长度,最小为 32
。
我们接受哪些安全算法。
我们允许从浏览器到身份验证器使用哪种传输方式。
让我们开始配置我们的 HTTP 路由以确保安全。在使用 FIDO2 之前,有几个处理器始终需要到位
BodyHandler
SessionHandler
还建议使用 StaticHandler
,因为整个过程既需要 vert.x 代码(我们目前正在探索的)和一个小的辅助 JavaScript
脚本。为了简化开发,vert.x 也通过 Maven 依赖提供了这种辅助功能
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-webauthn</artifactId>
</dependency>
<dependency> <!--(1)-->
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-webauthn</artifactId>
<version>${vertx.version}</version>
<classifier>client</classifier>
<type>js</type>
</dependency>
</dependencies>
提供了一个简单的辅助脚本,用于与 vert.x 后端交互。
现在我们可以像这样初始化 Web 路由器
final Router app = Router.router(vertx);
app.route() // (1)
.handler(StaticHandler.create());
app.post() // (2)
.handler(BodyHandler.create());
app.route() // (3)
.handler(SessionHandler
.create(LocalSessionStore.create(vertx)));
WebAuthnHandler webAuthnHandler = WebAuthnHandler.create(webAuthN) // (4)
.setOrigin(String.format("https://%s.nip.io:8443", System.getenv("IP")))
// required callback
.setupCallback(app.post("/webauthn/callback"))
// optional register callback
.setupCredentialsCreateCallback(app.post("/webauthn/register"))
// optional login callback
.setupCredentialsGetCallback(app.post("/webauthn/login"));
app.route()
.handler(webAuthnHandler);
app.route("/protected") // (5)
.handler(ctx ->
ctx.response()
.end(
"FIDO2 is Awesome!\n" +
"No Password phishing here!\n"));
为客户端应用程序提供服务(稍后详述)。
启用 POST 请求体的解析。
启用会话。
使用之前定义的配置挂载 webauthn 处理器。
示例安全路由。
现在我们有了一个最小的路由器,我们需要创建一个 HTTPS 服务器。请注意,这是一个必需的步骤,也是我们为开发环境创建自签名证书并使用自定义域名k 的原因。
vertx.createHttpServer(
new HttpServerOptions()
.setSsl(true)
.setKeyStoreOptions(
new JksOptions()
.setPath("certstore.jks")
.setPassword(System.getenv("CERTSTORE_SECRET"))))
.requestHandler(app)
.listen(8443, "0.0.0.0")
.onSuccess(v -> {
System.out.printf("Server: https://%s.nip.io:8443%n", System.getenv("IP"));
start.complete();
})
.onFailure(start::fail);
至此,我们的后端应用程序已完成,路由 /protected
应受 FIDO2 保护。运行应用程序如下:
IP=10.0.0.2 \
mvn exec:java
# The following line should be present in your console:
# Server listening at: https://10.0.0.2.nip.io:8443
您的浏览器给出关于自签名证书的警告是正常的
这是为了您的保护。在真实的应用程序中,您应该使用适当的 SSL 证书,例如由 Let's Encrypt 颁发的证书。
用您的浏览器导航到:https://10.0.0.2.nip.io:8443/protected
应该会显示一个 Forbidden
错误。下一步是创建一个最小的登录和注册 Web 应用程序。
对于客户端应用程序,不使用任何框架,以表明该脚本可以与任何框架一起使用,也可以独立使用。
我们将创建一个包含 3 个部分的极简 HTML 页面
一个表单,用户只需输入以下内容即可注册或登录:
显示名称:例如,一个用户友好的名称,如“John Doe”
用户名:一个唯一的用户名,如“[email protected]”
用于注册和登录的按钮
一个指向受保护资源的链接,只有在注册和登录后才允许访问。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>WebAuthn Howto</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="message"></div>
<div> <!--(1)-->
<label for="displayName">Display Name: </label>
<input id="displayName" name="displayName" type="text" value="Your Name"><br/>
<label for="username">Username: </label>
<input id="username" name="username" type="email" value="[email protected]">
</div>
<hr>
<div> <!--(2)-->
<button id="register">register</button>
<button id="login">login</button>
</div>
<hr>
<div> <!--(3)-->
<a href="/protected">Want some secret info?</a>
</div>
<script src="vertx-auth-webauthn-client.js"></script> <!--(4)-->
<script src="main.js"></script> <!--(5)-->
</body>
</html>
如上所述的第一部分。
如上所述的第二部分。
如上所述的第三部分。
已添加到项目依赖项中的辅助脚本
您的应用程序脚本
您可以在此处查看完整脚本。我们只介绍重要部分。
所包含的脚本 vertx-auth-webauthn-client.js
定义了一个全局类型 WebAuthn
。第一步是创建此对象的一个实例并对其进行配置,使其与我们的后端配置匹配。
const webAuthn = new WebAuthn({
callbackPath: '/webauthn/callback',
registerPath: '/webauthn/register',
loginPath: '/webauthn/login'
});
配置应该很简单,只需定义与您的后端路由匹配的路径即可。
接下来,我们需要为按钮添加事件处理程序。我们从注册操作开始
registerButton.onclick = () => {
webAuthn
.register({
name: document.getElementById('username').value,
displayName: document.getElementById('displayName').value
})
.then(() => {
displayMessage('registration successful');
})
.catch(err => {
displayMessage('registration failed');
console.error(err);
});
};
onclick
事件将只使用 Webauthn
对象。Webauthn 对象只有 2 个方法,它是基于 Promise
的,因此对于 vert.x 用户来说应该不难理解。如果您主要是 Java
vert.x 开发人员,只需将 JavaScript
Promise 视为 vert.x Future
,那么一切都会非常相似。
要注册用户,只需要 2 个必需属性
name
唯一的用户名,例如,电子邮件地址。
displayName
人类友好的文本描述,例如您的名字和姓氏。
填写表单并点击注册按钮后,用户应该会看到一个弹出窗口,要求授权注册。
触摸身份验证器后,流程应成功完成,显示
此时您可以关闭浏览器甚至打开一个不同的浏览器。注册过程已完成,因此您现在可以使用您的令牌从任何地方登录。
我们现在需要处理登录按钮的 onclick
事件
loginButton.onclick = () => {
webAuthn
.login({
name: document.getElementById('username').value
})
.then(() => {
displayMessage('You are logged in');
})
.catch(err => {
displayMessage('Invalid credential');
console.error(err);
});
};
正如 FIDO2 所描述的,使用 WebAuthn 是一种无密码身份验证,因此执行登录唯一需要的字段是
name
唯一的用户名,例如,电子邮件地址。
就像在注册屏幕中一样,将弹出一个窗口,告诉您有登录意图并请求用户授权。当您触摸身份验证器时,您可以看到
现在您已登录,终于可以尝试查看秘密信息链接了,它应该会给您一些类似的东西
在本操作指南中,我们介绍了
创建一个 Web 项目
使用 Webauthn 保护 Web 应用程序
在 webauthn-client.js 的帮助下编写客户端代码
希望您现在可以在下一个项目中使用 FIDO2/webauthn 了!
最后发布:2025-06-11 02:03:27 +0000。