跳到主要内容
ecdsa.com
Java · Kotlin

WebAuthn4J

WebAuthn4J,Spring Security 的通行密钥支持和 Keycloak 底层所用的 WebAuthn 验证库:用户验证没有隐藏的默认值,签名只接受 DER,计数器回退时直接判定失败。
许可证
Apache-2.0
检查的版本
0.31.11.RELEASE ·
源代码
github.com/webauthn4j/webauthn4j
文档
webauthn4j.github.io/webauthn4j/en/

兼容性概览

WebAuthn

WebAuthn4J 验证注册响应和身份验证响应——包括所有格式的证明声明、COSE 密钥、标志、计数器和签名——而构建选项的工作留给依赖方。Spring Security 和 Keycloak 在底层使用它。0.31.11 依赖 Jackson 3(tools.jackson.*),如果你的应用自身处理 JSON 时仍在使用 Jackson 2 的 com.fasterxml.jackson,就需要注意这一点;它的 API 还带有 JetBrains 的 @NotNull 注解:在把 org.jetbrains:annotations 加入类路径之前,用单文件 java 启动器针对它编译一直失败。

不存在可能被遗忘的默认用户验证策略:RegistrationParameters 和 AuthenticationParameters 把 userVerificationRequired 和 userPresenceRequired 作为必需参数。在检查中,未设置 UV 标志的注册和断言,传入 false 时验证通过,传入 true 时报错 UserNotVerifiedException。应像示例那样,让这个值与你在选项中发送的 userVerification 保持一致。

签名必须是 ASN.1 DER:64 字节原始 r‖s 格式的断言签名报错 BadSignatureException: Assertion signature is not valid.,而 high-S 的 DER 签名验证通过。计数器低于已存储值时,登录失败并报错 MaliciousCounterValueException: Malicious counter value is detected. Cloned authenticators exist in parallel.;而始终为 0 的计数器(同步通行密钥报告的正是这种情况)可以通过。Ed25519 凭证完成了端到端验证。

createNonStrictWebAuthnManager() 接受任何证明声明,不根据信任锚检查它们:如果你请求的证明为 none,这样做是对的;如果是否接受证明属于策略决定,这样做就是错的——这种情况下应配置信任锚并使用严格的管理器。

WebAuthn 查看器可以解码浏览器发送的数据,AAGUID 查询则能识别认证器型号,供制定证明策略时使用。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/webauthn4j/run.sh 于2026年10月20日在容器镜像 maven:3.9.16-eclipse-temurin-25 中运行了 WebAuthn4J 0.31.11.RELEASE;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • 用户验证参数

    mandatory

    userVerificationRequired 和 userPresenceRequired 是必需的构造函数参数;传入 false 时,没有 UV 标志的响应验证通过,传入 true 时则报错 UserNotVerifiedException。

    检查记录
    fact uv-requirement-argument = mandatory
    evidence uv-requirement-argument: registration with UV clear, userVerificationRequired false: verified; true: UserNotVerifiedException: Verifier is configured to check user verified, but UV flag in authenticatorData is not set.
    evidence uv-requirement-argument: RegistrationParameters and AuthenticationParameters take userVerificationRequired and userPresenceRequired as required booleans
  • 断言签名编码

    der

    带 DER 签名的正确 ES256 断言验证通过。

    检查记录
    fact webauthn-assertion-encoding = der
  • 原始 r‖s 断言签名

    rejected

    64 字节的原始 r‖s 签名报错 BadSignatureException: Assertion signature is not valid.

    检查记录
    fact webauthn-raw-signature = rejected
  • high-S 断言签名

    accepted

    把 s 替换为 n − s 的 DER 签名验证通过。

    检查记录
    fact webauthn-high-s = accepted
  • 签名计数器回退

    rejected

    已存储值为 5 时,计数器为 3 的断言报错 MaliciousCounterValueException;已存储值为 0 时,计数器为 0 的断言验证通过。

    检查记录
    fact webauthn-counter-regression = rejected
  • Ed25519 凭证

    supported

    Ed25519(COSE -8)凭证的注册和身份验证均成功。

    检查记录
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

用法

创建验证管理器和注册参数,显式声明用户验证和用户在场的要求。
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.data.RegistrationParameters;
import com.webauthn4j.data.PublicKeyCredentialParameters;
import com.webauthn4j.data.PublicKeyCredentialType;
import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier;
import com.webauthn4j.data.client.Origin;
import com.webauthn4j.data.client.challenge.DefaultChallenge;
import com.webauthn4j.server.ServerProperty;
import java.util.List;

public class Usage {
    public static void main(String[] args) {
        // "Non-strict" accepts every attestation format without trust anchors; pair it
        // with attestation "none" in the options, or configure trust anchors instead.
        WebAuthnManager manager = WebAuthnManager.createNonStrictWebAuthnManager();
        ServerProperty server = new ServerProperty(new Origin("https://example.com"), "example.com", new DefaultChallenge());

        // There is no default: every verification states UV and UP requirements explicitly.
        RegistrationParameters registration = new RegistrationParameters(server,
            List.of(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256)),
            true, true);
        // manager.verify(manager.parseRegistrationResponseJSON(json), registration) does the rest.
        System.out.println("uv required: " + registration.isUserVerificationRequired()
            + ", up required: " + registration.isUserPresenceRequired() + ", manager: " + manager.getClass().getSimpleName());
    }
}

代码片段测试工具会在容器镜像 maven:3.9.16-eclipse-temurin-25 中原样运行每个示例,只要输出不匹配,运行就会失败。

环境准备
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
命令
java -cp 'lib/*' Usage.java
输出必须匹配(正则表达式)
uv required: true, up required: true, manager: WebAuthnManager

库