Slide 1

Slide 1 text

Copyright © Yoshikazu Nojima 2025 Java開発者のためのパスキー入門 2025-06-07 JJUG CCC 2025 Spring 能島 良和 (@shiroica)

Slide 2

Slide 2 text

Copyright © Yoshikazu Nojima 2025 自己紹介 • 能島良和 • Red Hatでミドルウェア製品のテクニカルサポートを担当 • WebAuthn4Jというライブラリの開発者 https://github.com/webauthn4j/webauthn4j Keycloak、Spring、Quarkus等で採用 • Twitter:@shiroica • GitHub:ynojima 1

Slide 3

Slide 3 text

Copyright © Yoshikazu Nojima 2025 突然ですが・・・ この中にオンラインサービスのアカウントを 乗っ取られた経験のある方はいますか? 2

Slide 4

Slide 4 text

Copyright © Yoshikazu Nojima 2025 恥ずかしながら私はあります・・・ 3

Slide 5

Slide 5 text

Copyright © Yoshikazu Nojima 2025 原因 恐らく別のサービスから流出したID/Passwordによるリスト型攻撃。 ID/Passwordを色々なサービスで使いまわしていた為に攻撃されました。 (※仕事で使用していたパスワードとは別でした、念の為) 4

Slide 6

Slide 6 text

Copyright © Yoshikazu Nojima 2025 パスワードの限界 自分の不注意はさておきパスワードには以下の問題があります。 • フィッシング攻撃耐性 • フィッシング攻撃とは: 本物そっくりの偽物サイトを作成し、ユーザーを誤認させたうえでID/Passwordを入力させ詐取 • 精巧に作成されたフィッシングサイトは45%のユーザーが騙されるという研究結果も • リスト型攻撃耐性 • 脆弱な他サービスで流出したID/Passwordを用いた攻撃 • 自サービスは堅牢でも、ユーザーがパスワードを使いまわした場合、巻き添え被害の可能性 パスワード認証に代わる認証手段が必要 5

Slide 7

Slide 7 text

Copyright © Yoshikazu Nojima 2025 Web Authentication仕様とは W3Cで策定された、パスワード認証の問題点を克服したセキュアな認証を 実現するためのWeb標準。 ■主な特徴 • SSHのように公開鍵認証方式ベース • SSHにおける秘密鍵に対するパスフレーズ相当の、 秘密鍵を保護するためのローカル認証としてPINや指紋認証等の生体認証を利用可能 • PINや生体情報はローカルに留まりサーバーには電子署名のみが送信される為、安全性が高い • フィッシング攻撃、 リプレイ攻撃対策がプロトコルレベルで組込 6

Slide 8

Slide 8 text

Copyright © Yoshikazu Nojima 2025 デモ 7

Slide 9

Slide 9 text

Copyright © Yoshikazu Nojima 2025 Web Authenticationの認証フロー 8

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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 を生成・保存

Slide 12

Slide 12 text

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 を生成・保存 サーバーに保存されているのは公開鍵、 通信経路でやり取りされるのは署名なので流出しても影響は軽減

Slide 13

Slide 13 text

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(≒ドメイン)毎に保存されており、 フィッシング攻撃で他ドメインから認証要求を受けても 秘密鍵は不正利用からプロトコルレベルで保護

Slide 14

Slide 14 text

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 を生成・保存 署名対象にチャレンジ(ランダムデータ)を含めることで、 盗聴した正規のリクエストを繰り返すリプレイ攻撃を防止

Slide 15

Slide 15 text

Copyright © Yoshikazu Nojima 2025 WebAuthnが解決した課題、残った課題 WebAuthnが解決した課題 • フィッシング攻撃、リスト型攻撃に対して安全な認証を実現 解決出来なかった課題 • 一般ユーザーへの普及 • 原因 • 秘密鍵が格納された高価な認証デバイスが必要(¥5,000~) • セキュリティキーの故障・紛失時にアカウントへのアクセスを失う • バックアップ用に2個セキュリティキーを登録するのは非現実的 14

Slide 16

Slide 16 text

Copyright © Yoshikazu Nojima 2025 パスキーでの改善 15

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Copyright © Yoshikazu Nojima 2025 Hybrid Transportのデモ 17 左:ログインしたいPC 右:パスキーの保存されたAndroid

Slide 19

Slide 19 text

Copyright © Yoshikazu Nojima 2025 ここまでのまとめ WebAuthnとは • フィッシング攻撃、リスト型攻撃に対して安全な認証を実現した新しい認証方式 パスキーとは • 秘密鍵のクラウド経由での同期、バックアップ、スマートフォンの認証デバイス化等、 ユーザビリティを改善したWebAuthnの改良版の通称 18

Slide 20

Slide 20 text

Copyright © Yoshikazu Nojima 2025 パスキーの実装 19

Slide 21

Slide 21 text

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 を生成・保存

Slide 22

Slide 22 text

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 を生成・保存

Slide 23

Slide 23 text

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 を生成・保存

Slide 24

Slide 24 text

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 を生成・保存

Slide 25

Slide 25 text

Copyright © Yoshikazu Nojima 2025 Spring Security 24

Slide 26

Slide 26 text

Copyright © Yoshikazu Nojima 2025 Spring Security:6.4からパスキーに対応 25 https://docs.spring.io/spring-security/reference/servlet/authentication/passkeys.html

Slide 27

Slide 27 text

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 アプリとして実装が必要

Slide 28

Slide 28 text

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(); }

Slide 29

Slide 29 text

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 findByUserId(Bytes userId); }

Slide 30

Slide 30 text

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メソッドを 呼び出すのに必要なオプション一式を返すエンドポイントが提供

Slide 31

Slide 31 text

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の実装

Slide 32

Slide 32 text

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) }); }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Copyright © Yoshikazu Nojima 2025 Quarkus 33

Slide 35

Slide 35 text

Copyright © Yoshikazu Nojima 2025 Quarkusとは 34

Slide 36

Slide 36 text

Copyright © Yoshikazu Nojima 2025 Quarkus SecurityのPasskeysサポート 35 https://quarkus.io/guides/security-webauthn

Slide 37

Slide 37 text

Copyright © Yoshikazu Nojima 2025 code.quarkus.ioでプロジェクト生成 36

Slide 38

Slide 38 text

Copyright © Yoshikazu Nojima 2025 エンドポイントの有効化のための application.propertiesでの設定 37 quarkus.webauthn.enable-login-endpoint=true quarkus.webauthn.enable-registration-endpoint=true

Slide 39

Slide 39 text

Copyright © Yoshikazu Nojima 2025 永続化処理:WebAuthnUserProviderの実装 38 public interface WebAuthnUserProvider { public Uni> findByUsername(String username); public Uni findByCredentialId(String credentialId); public default Uni update(String credentialId, long counter) { return Uni.createFrom().voidItem(); } public default Uni store(WebAuthnCredentialRecord credentialRecord) { return Uni.createFrom().voidItem(); } public default Set getRoles(String username) { return Collections.emptySet(); } }

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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() フロントエンド 呼出 呼出 呼出

Slide 42

Slide 42 text

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) }); }

Slide 43

Slide 43 text

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() フロントエンド 呼出 呼出 呼出

Slide 44

Slide 44 text

Copyright © Yoshikazu Nojima 2025 実装例 43

Slide 45

Slide 45 text

Copyright © Yoshikazu Nojima 2025 パスキー導入時の考慮点 44

Slide 46

Slide 46 text

Copyright © Yoshikazu Nojima 2025 エンドユーザーに嫌われるパスキー 開発者目線では長所が多く、積極的に導入したくなるパスキーですが、 エンドユーザーも同じとは限りません。 45

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Copyright © Yoshikazu Nojima 2025 フォールバック手段(1):パスワード認証 実装 アカウント登録時にパスワードも登録させておき、 パスキー認証に失敗した場合はパスワード認証も選べるように 47 メリット 誰もが知ってる認証方式でユーザーが迷わない デメリット 当然フィッシング攻撃の対象になる フィッシング攻撃対策より、生体認証の利便性 目的でパスキーを採用している場合は選択肢

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Copyright © Yoshikazu Nojima 2025 まとめ 51

Slide 53

Slide 53 text

Copyright © Yoshikazu Nojima 2025 まとめ • パスキー認証の登録・認証処理のシーケンスの全体像を説明しました • パスキー認証の実装には、サーバーサイドはチャレンジの提供や、 クレデンシャルの登録、認証等のエンドポイントの提供が必要 • SpringやQuarkusを使えば、フレームワークが提供するエンドポイントが 利用可能 • セキュリティとのバランスをとりながら、パスキーが利用出来ない場合の 考慮が重要 52

Slide 54

Slide 54 text

Copyright © Yoshikazu Nojima 2025 おすすめ資料 53

Slide 55

Slide 55 text

Copyright © Yoshikazu Nojima 2025 Appendix 54

Slide 56

Slide 56 text

Copyright © Yoshikazu Nojima 2025 パスキー実装時の悩み 55 どうやって自動テスト書く?

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Copyright © Yoshikazu Nojima 2025 59 承認操作なしで応答を返す、 エミュレーターが欲しい

Slide 61

Slide 61 text

Copyright © Yoshikazu Nojima 2025 Chromeの開発者ツール 60 Chromeの開発者ツール のWebAuthnタブから、 仮想Authenticatorの 追加削除が可能

Slide 62

Slide 62 text

Copyright © Yoshikazu Nojima 2025 WebAuthn WebDriver Extension WebDriver APIで操作されているブラウザに対し、 Authenticatorのエミュレータを追加・削除するための拡張API 61

Slide 63

Slide 63 text

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追加

Slide 64

Slide 64 text

Copyright © Yoshikazu Nojima 2025 Chromiumの仮想Authenticatorで十分か? • 仮想Authenticatorの設定項目は多くない (residentKey, uv, transport等) • 残念ながらパスキーは環境依存の問題が発生しがち • ChromeでのE2Eテストは、よく使われる環境をカバー出来、 テスト効率を向上させるが、依然として手動テストも重要 63

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Copyright © Yoshikazu Nojima 2025 65