1. 简介
登录表单长期以来一直是需要身份验证的Web服务的标准功能。然而随着安全问题的日益突出,传统文本密码的弱点逐渐暴露:它们可能被猜测、拦截或泄露,导致安全事件并造成财务和声誉损失。
之前的替代方案(如mTLS、安全卡等)试图解决这个问题,但带来了糟糕的用户体验和额外成本。
本教程将探讨Passkeys(又称WebAuthn),这是一种提供密码安全替代方案的标准。我们将演示如何快速为Spring Boot应用添加这种身份验证机制支持。
2. 什么是Passkey?
Passkeys或WebAuthn是W3C联盟定义的标准API,允许Web浏览器应用管理公钥并将其注册给特定服务提供商。
典型注册流程如下:
- 用户在服务上创建新账户,初始凭证通常是熟悉的用户名/密码
- 注册后,用户进入个人资料页面选择"创建passkey"
- 系统显示passkey注册表单
- 用户填写必要信息(如密钥标签,便于后续选择)并提交
- 系统将passkey保存到数据库并与用户账户关联,同时密钥的私钥部分会存储在用户设备上
- passkey注册完成
密钥注册完成后,用户可使用存储的passkey访问服务。根据浏览器和设备的安全配置,登录可能需要指纹扫描、解锁手机或类似操作。
Passkey由两部分组成:浏览器发送给服务提供商的公钥,以及保留在本地设备上的私钥。
此外,客户端API实现确保特定passkey只能用于注册它的同一站点。
3. 在Spring Boot应用中添加Passkeys
创建一个简单的Spring Boot应用来测试passkeys。我们的应用将只有一个欢迎页面,显示当前用户名和passkey注册页面的链接。
首先添加必要依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>0.28.5.RELEASE</version>
</dependency>
这些依赖的最新版本可在Maven Central获取:
⚠️ 重要:WebAuthn支持需要Spring Boot 3.4.0或更高版本
4. Spring Security配置
从Spring Security 6.4(通过spring-boot-starter-security
依赖默认包含)开始,配置DSL通过webauthn()
方法原生支持passkeys。
@Bean
SecurityFilterChain webauthnFilterChain(HttpSecurity http, WebAuthNProperties webAuthNProperties) {
return http.authorizeHttpRequests( ht -> ht.anyRequest().authenticated())
.formLogin(withDefaults())
.webAuthn(webauth ->
webauth.allowedOrigins(webAuthNProperties.getAllowedOrigins())
.rpId(webAuthNProperties.getRpId())
.rpName(webAuthNProperties.getRpName())
)
.build();
}
此配置提供以下功能:
- 登录页面会出现"使用passkey登录"按钮
- 提供
/webauthn/register
注册页面
为正常运行,必须为webauthn
配置器提供以下属性:
-
allowedOrigins
:站点外部URL(必须使用HTTPS,localhost除外) -
rpId
:应用标识符(必须是匹配allowedOrigin
主机名部分的有效域名) -
rpName
:浏览器在注册/登录过程中可能使用的友好名称
但此配置有个关键缺陷:应用重启后注册的密钥会丢失。这是因为Spring Security默认使用内存凭证存储,不适合生产环境。
稍后我们将解决这个问题。
5. Passkey功能演示
配置好passkey后,让我们快速体验应用功能。使用mvn spring-boot:run
或IDE启动应用后,浏览器访问http://localhost:8080:
Spring应用的默认登录页面现在会包含"使用passkey登录"按钮。由于尚未注册密钥,需使用application.yaml
中配置的用户名/密码(alice/changeit)登录:
如预期所示,我们以Alice身份登录。点击"Register PassKey"链接进入注册页面:
只需提供标签(如baeldung-demo
)并点击"Register"按钮。后续操作取决于设备类型(桌面/移动/平板)和操作系统(Windows/Linux/Mac/Android),最终会新增一个密钥:
例如在Windows版Chrome中,对话框会提示创建新密钥并存储到浏览器原生密码管理器,或使用系统自带的Windows Hello功能。
现在退出应用并测试新密钥。首先访问http://localhost:8080/logout确认退出,然后在登录表单点击"使用passkey登录"。浏览器会显示密钥选择对话框:
选择可用密钥后,设备会执行额外身份验证挑战(如Windows Hello的指纹或面部识别)。验证成功后,设备私钥会签名挑战并发送到服务器,服务器使用之前存储的公钥验证。最终登录成功并显示欢迎页面。
6. Passkey存储库
如前所述,Spring Security的默认passkey配置不提供密钥持久化。要解决这个问题,需要实现以下接口:
-
PublicKeyCredentialUserEntityRepository
-
UserCredentialRepository
6.1. PublicKeyCredentialUserEntityRepository
该服务管理PublicKeyCredentialUserEntity
实例,将标准UserDetailsService
管理的用户账户映射到用户账户标识符。该实体包含以下属性:
-
name
:账户的友好名称标识符 -
id
:用户账户的不透明标识符 -
displayName
:账户名的备用版本,用于显示
注意当前实现假设name
和id
在特定认证域内唯一。
通常,此表中的条目与UserDetailsService
管理的账户是1:1关系。
实现代码使用Spring Data JDBC将这些字段存储到PASSKEY_USERS
表。
6.2. UserCredentialRepository
管理CredentialRecord
实例,存储注册过程中从浏览器接收的实际公钥。该实体包含W3C文档规定的所有推荐属性,以及一些额外属性:
-
userEntityUserId
:拥有此凭证的PublicKeyCredentialUserEntity
标识符 -
label
:用户定义的凭证标签(注册时分配) -
lastUsed
:凭证最后使用日期 -
created
:凭证创建日期
注意CredentialRecord
与PublicKeyCredentialUserEntity
是N:1关系,这体现在存储库方法中(如findByUserId()
返回CredentialRecord
列表)。
我们的实现通过在PASSKEY_CREDENTIALS
表中使用外键确保引用完整性。
7. 测试
虽然可以使用模拟请求测试基于passkey的应用,但这种测试的价值有限。大多数失败场景与客户端相关,需要使用自动化工具驱动的真实浏览器进行集成测试。
这里我们使用Selenium实现"正常流程"场景演示。特别使用VirtualAuthenticator
功能配置WebDriver
,模拟注册和登录页面的交互。
例如创建带VirtualAuthenticator
的驱动:
@BeforeEach
void setupTest() {
VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions()
.setIsUserVerified(true)
.setIsUserConsenting(true)
.setProtocol(VirtualAuthenticatorOptions.Protocol.CTAP2)
.setHasUserVerification(true)
.setHasResidentKey(true);
driver = new ChromeDriver();
authenticator = ((HasVirtualAuthenticator) driver).addVirtualAuthenticator(options);
}
获取authenticator
实例后,可模拟不同场景(成功/失败登录、注册等)。我们的实时测试包含完整流程:
- 使用用户名/密码初始登录
- 注册passkey
- 退出登录
- 使用passkey登录
8. 结论
本教程展示了如何在Spring Boot Web应用中使用Passkeys,包括Spring Security设置和生产环境所需的密钥持久化支持。通过合理配置和存储实现,可以显著提升应用的安全性,同时改善用户体验。