Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Passkeys for Java Developers

Passkeys for Java Developers

Avatar for Yoshikazu Nojima

Yoshikazu Nojima

June 07, 2025
Tweet

More Decks by Yoshikazu Nojima

Other Decks in Programming

Transcript

  1. Copyright © Yoshikazu Nojima 2025 自己紹介 • 能島良和 • Red

    Hatでミドルウェア製品のテクニカルサポートを担当 • WebAuthn4Jというライブラリの開発者 https://github.com/webauthn4j/webauthn4j Keycloak、Spring、Quarkus等で採用 • Twitter:@shiroica • GitHub:ynojima 1
  2. Copyright © Yoshikazu Nojima 2025 パスワードの限界 自分の不注意はさておきパスワードには以下の問題があります。 • フィッシング攻撃耐性 •

    フィッシング攻撃とは: 本物そっくりの偽物サイトを作成し、ユーザーを誤認させたうえでID/Passwordを入力させ詐取 • 精巧に作成されたフィッシングサイトは45%のユーザーが騙されるという研究結果も • リスト型攻撃耐性 • 脆弱な他サービスで流出したID/Passwordを用いた攻撃 • 自サービスは堅牢でも、ユーザーがパスワードを使いまわした場合、巻き添え被害の可能性 パスワード認証に代わる認証手段が必要 5
  3. Copyright © Yoshikazu Nojima 2025 Web Authentication仕様とは W3Cで策定された、パスワード認証の問題点を克服したセキュアな認証を 実現するためのWeb標準。 ▪主な特徴

    • SSHのように公開鍵認証方式ベース • SSHにおける秘密鍵に対するパスフレーズ相当の、 秘密鍵を保護するためのローカル認証としてPINや指紋認証等の生体認証を利用可能 • PINや生体情報はローカルに留まりサーバーには電子署名のみが送信される為、安全性が高い • フィッシング攻撃、 リプレイ攻撃対策がプロトコルレベルで組込 6
  4. Copyright © Yoshikazu Nojima 2025 クレデンシャル の保存 Web Authenticationの登録フロー(概要) 認証デバイス

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 クレデンシャル(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,RP固有公開鍵,拡張)を生成 challengeを提供 Storage RP固有秘密鍵 を保存 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge を生成・保存 challenge の読込 RP固有鍵ペアを生成 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session
  5. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 Web Authenticationの認証フロー(概要) 認証デバイス

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 challenge を生成・保存
  6. Copyright © Yoshikazu Nojima 2025 クレデンシャル (公開鍵)の読込 プロトコルレベルで組み込まれたセキュリティ(1): クレデンシャル流出対策としての公開鍵認証 パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 challenge を生成・保存 サーバーに保存されているのは公開鍵、 通信経路でやり取りされるのは署名なので流出しても影響は軽減
  7. Copyright © Yoshikazu Nojima 2025 クレデンシャル (公開鍵)の読込 プロトコルレベルで組み込まれたセキュリティ(2): フィッシング攻撃対策 パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 challenge を生成・保存 秘密鍵はRpId(≒ドメイン)毎に保存されており、 フィッシング攻撃で他ドメインから認証要求を受けても 秘密鍵は不正利用からプロトコルレベルで保護
  8. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 プロトコルレベルで組み込まれたセキュリティ(3): リプレイ攻撃対策としてのチャレンジデータ パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 challenge を生成・保存 署名対象にチャレンジ(ランダムデータ)を含めることで、 盗聴した正規のリクエストを繰り返すリプレイ攻撃を防止
  9. Copyright © Yoshikazu Nojima 2025 WebAuthnが解決した課題、残った課題 WebAuthnが解決した課題 • フィッシング攻撃、リスト型攻撃に対して安全な認証を実現 解決出来なかった課題

    • 一般ユーザーへの普及 • 原因 • 秘密鍵が格納された高価な認証デバイスが必要(¥5,000~) • セキュリティキーの故障・紛失時にアカウントへのアクセスを失う • バックアップ用に2個セキュリティキーを登録するのは非現実的 14
  10. Copyright © Yoshikazu Nojima 2025 従来のWebAuthnに対して、以下の拡張を加えたもの※: • Multi-device credential •

    iCloud Key ChainやGoogle Password Manager等を通じて デバイス間で同期されたWebAuthnのCredential • Hybrid Transport • スマートフォンをセキュリティキーとして利用可能にするための技術 • 認証デバイスとクライアントの新しい接続方式 • セキュリティキーではUSBやNFCでPCと接続していたが、スマートフォンを接続するために、 QRコードとBLE Advertisementを用いてローカルでE2E暗号化用の鍵交換した上で、インターネット 上のトンネルサービス経由でクライアントに接続するハイブリッドな接続方式 • 初めて利用するデバイスなど、Passkeyが同期されていないデバイスでのログイン時に有用 ※実はパスキーという用語に統一的な定義は存在しないが、今回はこのように定義。 この2つの拡張はWebAuthn Level3として策定中 パスキーとは 16
  11. Copyright © Yoshikazu Nojima 2025 ここまでのまとめ WebAuthnとは • フィッシング攻撃、リスト型攻撃に対して安全な認証を実現した新しい認証方式 パスキーとは

    • 秘密鍵のクラウド経由での同期、バックアップ、スマートフォンの認証デバイス化等、 ユーザビリティを改善したWebAuthnの改良版の通称 18
  12. Copyright © Yoshikazu Nojima 2025 クレデンシャル の保存 WebAuthn APIが提供する範囲(登録処理) パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 クレデンシャル(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,RP固有公開鍵,拡張)を生成 challengeを提供 Storage RP固有秘密鍵 を保存 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 RP固有鍵ペアを生成 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session navigator.credentials.create() challenge を生成・保存
  13. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 WebAuthn APIが提供する範囲(認証処理) パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 navigator.credentials.get() challenge を生成・保存
  14. Copyright © Yoshikazu Nojima 2025 クレデンシャル の保存 サーバーサイドで必要な処理(登録フロー) パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 クレデンシャル(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,RP固有公開鍵,拡張)を生成 challengeを提供 Storage RP固有秘密鍵 を保存 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 RP固有鍵ペアを生成 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session チャレンジ提供用エンドポイント クレデンシャル登録用エンドポイント challenge を生成・保存
  15. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 サーバーサイドで必要な処理(認証フロー) パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 チャレンジ提供用エンドポイント アサーション認証用エンドポイント challenge を生成・保存
  16. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 Spring Securityが提供するエンドポイント(登録フロー) パスキープロバイダ

    ブラウザ サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 challenge を生成・保存 PublicKeyCredentialCreationOptionsFilter WebAuthnRegistrationFilter アプリとして実装が必要
  17. Copyright © Yoshikazu Nojima 2025 必要な設定 27 必要な依存関係の定義 JavaConfigによる設定 implementation

    'org.springframework.security:spring-security-web' implementation 'com.webauthn4j:webauthn4j-core:0.28.4.RELEASE' @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .webAuthn((webAuthn) -> webAuthn .rpName("Spring Security Relying Party") .rpId("localhost") .allowedOrigins("http://localhost:8080") ) //中略 return http.build(); }
  18. Copyright © Yoshikazu Nojima 2025 永続化処理:UserCredentialRepositoryの実装 28 public interface UserCredentialRepository

    { /** * Deletes an entry by credential id * @param credentialId {@link CredentialRecord#getCredentialId()} */ void delete(Bytes credentialId); /** * Saves a {@link CredentialRecord} * @param credentialRecord the {@link CredentialRecord} to save. */ void save(CredentialRecord credentialRecord); /** * Finds an entry by credential id. * @param credentialId {@link CredentialRecord#getCredentialId()} * @return the {@link CredentialRecord} or null if not found. */ CredentialRecord findByCredentialId(Bytes credentialId); /** * Finds all {@link CredentialRecord} instances for a specific user. * @param userId the {@link PublicKeyCredentialUserEntity#getId()} to search for a user. * @return all {@link CredentialRecord} instances for a specific user or empty if no results found. Never null. * @see PublicKeyCredentialUserEntityRepository */ List<CredentialRecord> findByUserId(Bytes userId); }
  19. Copyright © Yoshikazu Nojima 2025 エンドポイントの動作 $ curl 'http://localhost:8080/webauthn/authenticate/options' ¥

    -X 'POST' ¥ -b 'JSESSIONID=E900608D095D079450E6F8BD542C4D7A' ¥ -H 'Origin: http://localhost:8080' ¥ -H 'X-CSRF-TOKEN: VWLLI-UXajosGnU- IycN85Z_n5xB5H984M6f3444vnJTYKaRNwetQYElWAsBLkVfQgo5x6RHsv4ghx5Rg_2vvu8Nj0dgVJPw’ { "challenge": "y3JPvIhVloNoi9k4WlH_Zj5zest8rs8oHbgu8flYv5M", "timeout": 300000, "rpId": "localhost", "allowCredentials": [], "userVerification": "preferred", "extensions": {} } 29 チャレンジだけでなく、WebAuthnの登録APIであるnavigator.credentials.createメソッドを 呼び出すのに必要なオプション一式を返すエンドポイントが提供
  20. Copyright © Yoshikazu Nojima 2025 クレデンシャル の保存 登録処理の全体像 パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 クレデンシャル(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,RP固有公開鍵,拡張)を生成 challengeを提供 Storage RP固有秘密鍵 を保存 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 RP固有鍵ペアを生成 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session チャレンジ提供用エンドポイント(PublicKeyCredentialCreationOptionsFilter) クレデンシャル登録用エンドポイント(WebAuthnRegistrationFilter) challenge を生成・保存 navigator.credentials.create() フロントエンド 呼出 呼出 呼出 UserCredentialRepositoryの実装
  21. Copyright © Yoshikazu Nojima 2025 フロントエンドの実装例 31 async function createCredential(label){

    // オプション(含チャレンジ)提供用エンドポイントの呼出 const registerOptionsEndpoint = `/webauthn/register/options` const response = await fetch(registerOptionsEndpoint) const publicKeyCredentialCreationOptionsJSON = await response.json() const credentialCreationOptions = PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyCredentialCreationOptionsJSON); // パスキークレデンシャルの作成 const publicKeyCredential = await navigator.credentials.create({ publicKey: credentialCreationOptions}); const registrationResponseJSON = publicKeyCredential.toJSON(); const registrationMessage = { publicKey: { registrationResponseJSON, label } }; console.log(registrationResponseJSON); // クレデンシャル登録用エンドポイントの呼出 const registerEndpoint = `/webauthn/register` await fetch(registerEndpoint, { method : 'POST', body: JSON.stringify(registrationResponseJSON) }); }
  22. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 認証処理の全体像 パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 チャレンジ提供用エンドポイント(PublicKeyCredentialRequestOptionsFilter) アサーション認証用エンドポイント challenge を生成・保存 navigator.credentials.get() フロントエンド 呼出 呼出 呼出 UserCredentialRepositoryの実装 ※認証成功時UserDetailsServiceを通じて取得したUserDetailsに基づくWebAuthnAuthenticationを返却 WebAuthnAuthenticationFilter WebAuthnAuthenticationProvider ※Spring Securityのアーキテクチャの解説は4月のJJUGナイトセミナー「ハイレベルSpring Security」がおススメ https://www.docswell.com/s/MasatoshiTada/K1R8JP-high-level-spring-security#p1
  23. Copyright © Yoshikazu Nojima 2025 永続化処理:WebAuthnUserProviderの実装 38 public interface WebAuthnUserProvider

    { public Uni<List<WebAuthnCredentialRecord>> findByUsername(String username); public Uni<WebAuthnCredentialRecord> findByCredentialId(String credentialId); public default Uni<Void> update(String credentialId, long counter) { return Uni.createFrom().voidItem(); } public default Uni<Void> store(WebAuthnCredentialRecord credentialRecord) { return Uni.createFrom().voidItem(); } public default Set<String> getRoles(String username) { return Collections.emptySet(); } }
  24. Copyright © Yoshikazu Nojima 2025 エンドポイントの動作 $ curl http://localhost:8080/q/webauthn/register-options-challenge?username=ynojima {

    "rp": { "id": "localhost", "name": "Quarkus server" }, "user": { "id": "Baeobf2jRmqbFa1nLz_rDg", "name": "ynojima", "displayName": "ynojima" }, "challenge": "nLZXvZrAz2rTU0erERIGrRsqY3zl8bW57-IUmzPzEX2FSR50yrGKWy-QPsMtDy6y6y9TWXOlf- _AcQZNSg6wWQ", "pubKeyCredParams": [ { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257} ], "timeout": 300000, "excludeCredentials": [], "authenticatorSelection": { "requireResidentKey": true, "residentKey": "required", "userVerification": "required" }, "attestation": "none", "extensions": {} } 39
  25. Copyright © Yoshikazu Nojima 2025 クレデンシャル の保存 登録処理の全体像 パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 クレデンシャル(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,RP固有公開鍵,拡張)を生成 challengeを提供 Storage RP固有秘密鍵 を保存 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 RP固有鍵ペアを生成 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session チャレンジ提供用エンドポイント クレデンシャル登録用エンドポイント challenge を生成・保存 navigator.credentials.create() フロントエンド 呼出 呼出 呼出
  26. Copyright © Yoshikazu Nojima 2025 フロントエンドの実装例 41 async function createCredential(username){

    // オプション(含チャレンジ)提供用エンドポイントの呼出 const registerOptionsChallengeEndpoint = username ? `/q/webauthn/register-options-challenge?username=${encodeURIComponent(username)}` : “/q/webauthn/register-options-challenge” const response = await fetch(registerOptionsChallengeEndpoint) const publicKeyCredentialCreationOptionsJSON = await response.json() const credentialCreationOptions = PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyCredentialCreationOptionsJSON); // パスキークレデンシャルの作成 const publicKeyCredential = await navigator.credentials.create({ publicKey: credentialCreationOptions}); const registrationResponseJSON = publicKeyCredential.toJSON(); console.log(registrationResponseJSON); // クレデンシャル登録用エンドポイントの呼出 const registerEndpoint = username ? `/q/webauthn/register?username=${encodeURIComponent(username)}` : “/q/webauthn/register” await fetch(registerEndpoint, { method : 'POST', body: JSON.stringify(registrationResponseJSON) }); }
  27. Copyright © Yoshikazu Nojima 2025 クレデンシャル の読込 認証処理の全体像 パスキープロバイダ ブラウザ

    サーバー • Credential Id • Authenticator data • 署名 を送信 アサーション(Credential Id, Authenticator data, Client Data, 署名、etc.)を送信 Client dataのハッシュを送信 Authenticator data(RpIdのハッシュ, フラグ,カウンタ,拡張)を生成 Storage RP固有秘密鍵 を読込 Client data (challenge, origin, etc.)を生成 Client dataのハッシュと Authenticator dataに対する署名を生成 challenge の読込 クレデンシャルに対し、 ドメイン(RP Id)、署名、 challenge、その他の検証 DB Session challengeを提供 チャレンジ提供用エンドポイント アサーション認証用エンドポイント challenge を生成・保存 navigator.credentials.get() フロントエンド 呼出 呼出 呼出
  28. Copyright © Yoshikazu Nojima 2025 パスキーの落とし穴 様々な理由でやっぱり利用できないパスキー • ブラウザにGoogleアカウント/Apple IDでログインしてないから同期されていない

    • 個人のGoogleアカウントと会社のGoogle Workspaceアカウントだから同期されていない • iCloud KeyChainにパスキーを保存するiPhoneのSafariと、 Google Password Managerを使うデスクトップ版Chromeの組み合わせなので同期されない ※iPhoneの設定でGoogle Password Managerを明示的に有効にしていれば同期可能 • 同期に未対応のMicrosoft Edgeを使っている • Bluetoothが無効/搭載されていないマシンだからHybrid Transportが動かない パソコン・スマートフォンに慣れた中級者以上なら兎も角、 初心者に自己解決が難しい問題が起きがち 最後はフラストレーションを溜めてサービスから離脱してしまう フォールバック手段としての他の認証方式との組み合わせが必要 46
  29. Copyright © Yoshikazu Nojima 2025 フォールバック手段(1):パスワード認証 実装 アカウント登録時にパスワードも登録させておき、 パスキー認証に失敗した場合はパスワード認証も選べるように 47

    メリット 誰もが知ってる認証方式でユーザーが迷わない デメリット 当然フィッシング攻撃の対象になる フィッシング攻撃対策より、生体認証の利便性 目的でパスキーを採用している場合は選択肢
  30. Copyright © Yoshikazu Nojima 2025 フォールバック手段(2):メール認証(マジックリンク方式) 実装 1. ユーザーがユーザー名を入力 2.

    登録済メールアドレスに対して認証コード付URL(マジックリンク)を送付 3. ユーザーが受信した認証コード付URLを開いた先のブラウザで認証セッション確立 48 メリット メールで送付した認証コード付URL(マジック リンク)をブラウザで開かせることで認証する ので、フィッシング攻撃されない デメリット • メールという通信経路のセキュリティに依存 • ログインに使用している端末と、メールが開 ける端末が異なる場合に利用できない (例:会社PCで開きたいが、登録メールア ドレスはプライベートのメールアドレス)
  31. Copyright © Yoshikazu Nojima 2025 フォールバック手段(3):メール認証(認証コード方式) 実装 1. ユーザーがユーザー名を入力 2.

    登録済メールアドレスに対して認証コードを送付 3. ユーザーが受信した認証コードを元のブラウザ上の画面に入力し、認証セッション確立 49 メリット • ログインに使用している端末と、メールが開 ける端末が異なっても問題ない デメリット • メールという通信経路のセキュリティに依存 • フィッシング攻撃の対象になる フィッシング攻撃対策が強く求められない ユースケースなら有効 Spring Security 6.4でOne-Time Tokenとしてサポート追加: https://docs.spring.io/spring-security/reference/servlet/authentication/onetimetoken.html
  32. Copyright © Yoshikazu Nojima 2025 フォールバック手段(4):ソーシャルログイン(SSO) 実装 「Googleで認証」「Apple IDで認証」といった外部のIdPに認証を委譲する方式 50

    メリット • 外部IdPでログイン後、正しいURLにリダイ レクトされるのでフィッシングされない ※外部IdP自体がフィッシング攻撃の対象になる可能性はある デメリット • 外部IdPのセキュリティに依存 • エンドユーザーが外部IdPのアカウントを 保持していることが前提 Quarkus SecurityはOIDCサポートだけでなく、主要なIdPに対するプリセット設定を提供: https://ja.quarkus.io/guides/security-openid-connect-providers
  33. Copyright © Yoshikazu Nojima 2025 まとめ • パスキー認証の登録・認証処理のシーケンスの全体像を説明しました • パスキー認証の実装には、サーバーサイドはチャレンジの提供や、

    クレデンシャルの登録、認証等のエンドポイントの提供が必要 • SpringやQuarkusを使えば、フレームワークが提供するエンドポイントが 利用可能 • セキュリティとのバランスをとりながら、パスキーが利用出来ない場合の 考慮が重要 52
  34. Copyright © Yoshikazu Nojima 2025 E2Eテストで扱う典型的な認証フロー(パスワード認証) 1. 登録画面に遷移する 2. 登録画面でユーザー名と

    パスワードを入力する 3. 登録ボタンを押す 4. 認証画面に遷移する 5. 認証画面でユーザー名と パスワードを入力する 6. 認証画面で認証ボタンを押す 56 自動化は容易
  35. Copyright © Yoshikazu Nojima 2025 E2Eテストで扱う典型的な認証フロー(パスキー認証) 1. 登録画面に遷移する 2. 登録画面でユーザー名を入力する

    3. パスキーの登録ボタンを押す 4. パスキープロバイダー側で承認操作を行う 5. 登録ボタンを押す 6. 認証画面に遷移する 7. 認証画面で認証ボタンを押す 8. パスキープロバイダーで承認操作を行う 57
  36. Copyright © Yoshikazu Nojima 2025 E2Eテストで扱う典型的な認証フロー(パスキー認証) 1. 登録画面に遷移する 2. 登録画面でユーザー名を入力する

    3. パスキーの登録ボタンを押す 4. パスキープロバイダー側で承認操作を行う 5. 登録ボタンを押す 6. 認証画面に遷移する 7. 認証画面で認証ボタンを押す 8. パスキープロバイダーで認証操作を行う 58 手動操作が必要
  37. Copyright © Yoshikazu Nojima 2025 selenium-javaでのテストコード例 62 public class RegistrationAndAuthenticationE2ETest

    extends E2ETestBase{ @Test public void test() { VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions(); ((HasVirtualAuthenticator) driver).addVirtualAuthenticator(options); // Registration SignupComponent signupComponent = new SignupComponent(driver); signupComponent.navigate(); signupComponent.setFirstname(“John”); signupComponent.setLastname(“Doe”); signupComponent.setUsername(“[email protected]”); signupComponent.setPassword(“password”); signupComponent.clickAddAuthenticator(); signupComponent.getResidentKeyRequirementDialog().clickNo(); signupComponent.waitRegisterClickable(); signupComponent.clickRegister(); // Password authentication wait.until(ExpectedConditions.urlToBe(“http://localhost:8080/angular/login”)); PasswordLoginComponent passwordLoginComponent = new PasswordLoginComponent(driver); passwordLoginComponent.setUsername(“[email protected]”); passwordLoginComponent.setPassword(“password”); passwordLoginComponent.clickLogin(); // 2nd-factor authentication AuthenticatorLoginComponent authenticatorLoginComponent = new AuthenticatorLoginComponent(driver); // nop wait.until(ExpectedConditions.urlToBe(“http://localhost:8080/angular/profile”)); } テストコード自体は ページオブジェクト パターンに則った Seleniumの普通の テストコード 仮想Authenticator追加
  38. Copyright © Yoshikazu Nojima 2025 Chromiumの仮想Authenticatorで十分か? • 仮想Authenticatorの設定項目は多くない (residentKey, uv,

    transport等) • 残念ながらパスキーは環境依存の問題が発生しがち • ChromeでのE2Eテストは、よく使われる環境をカバー出来、 テスト効率を向上させるが、依然として手動テストも重要 63
  39. Copyright © Yoshikazu Nojima 2025 まとめ • パスキーはパスキープロバイダーでユーザーの承認操作が必要 • E2Eテストを自動化する上で承認操作が障害

    • ChrimiumはWebAuthn WebDriver Extension APIを通じて 仮想Authenticatorに実装を切替、承認ジェスチャーをスキップ可能 • Chrimium以外のカバー、パスキープロバイダの実装差異のカバーを するために手動テストも依然として必要 • パスキーでもE2Eテストを書いていきましょう! 64