KeycloakでOpenID Connect Client Initiated Backchannel Authentication (CIBA)を試す

  •  
 
ホス2021年9月30日 - 09:27 に投稿

本記事で試すClient Initiated Backchannel Authentication (CIBA)について

  • CIBAの基本的な知識は本記事では解説しておりません。別途インターネット上の記事などをご参照ください。
  • 本記事ではPollモードを試します。ただし本来行うべき定期的にリクエストを送るPollingを行わず、都度手動でcurlを使ってリクエストを出します。
  • Keycloakバージョン13.0.0以降で利用できます。
    • 本記事の手順は、Keycloakバージョン15.0.0で検証しています。

CIBAに登場するのは以下の4つです。

  • クライアント
    • 認可を受けて何かをします。WEBアプリだったりIoTデバイスだったりするかもしれません。
    • 本記事ではクライアントのプログラムは用意せず、curlコマンドを利用します。
    • 認可を受けると「アクセストークン」がもらえて、クライアントはこのアクセストークンを使って何かをしますが、本記事ではアクセストークンをもらうところまでです。
  • 認可サーバ
    • クライアントから認可のリクエストを受け、ユーザに認可を求め、ユーザが認可を承認または拒否した結果をクライアントに伝えます。
    • 本記事では、Keycloakが担います。
  • 認証デバイス / Authentication Device (AD)
    • クライアントから認可のリクエストが来たときにユーザに認可を与えてもらうためのデバイスなりサイトなりです。
    • ADがどんなものなのかは、CIBAでは一部を除き定義されておりません。
    • 本記事では、適当なPHPアプリケーションを用意しますが、認証すらしない実装なので実用的ではありません、学習用です。
  • ユーザ
    • 認証デバイスを操作して、クライアントに対して認可を与える人間です。
    • 本記事では、curlを手動で実行します。

Keycloakのインストール・セットアップ

本記事ではKeycloakがインストール済みで、管理者アカウントでKeycloakの管理コンソールにアクセスできる前提で進めます。

CIBAの有効化設定

  1. Keycloakに内蔵されているAuthentication Channel Providerを有効化します。

    /opt/jboss/keycloak/bin/jboss-cli.sh -c
    /subsystem=keycloak-server/spi=ciba-auth-channel/:add(default-provider=ciba-http-auth-channel)
    /subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-http-auth-channel:add(enabled=true,properties={httpAuthenticationChannelUri=${env.AUTHENTICATION_ENTITY_URL}})
    exit
    

AD(認証デバイス)相当のPHPプログラムを適当なWEBサーバ上に構築

  1. PHPが動作可能なHTTPサーバを用意します。

    • 構築手順は割愛します。
  2. PHP製の簡易的なAuthentication Device(AD)のプログラムを用意します。

    vi /var/www/html/AD.php
    
    <?php
    #KeycloakからのPOSTリクエストに含まれるAuthorizeの情報をとりあえずログに出力する(あとで使う)
    $headers = getallheaders();
    $bearer = $headers['Authorization'];
    error_log($bearer, 0);
    #Keycloakからのリクエストに対して201レスポンスを返す(201を返すのはCIBAの仕様)
    header('Content-Type: application/json; charset=UTF-8');
    header( "HTTP/1.1 201 Created" );
    ?>
    
    • 認可サーバ(Keycloak)から認証デバイスへリクエストが届くと、Keycloakに認証成否を通知するときに必要となるAuthenticateヘッダの内容が以下のログに出力されるはずです。(CentOS7、Apache httpdサーバで検証)
    tail -f /var/log/httpd/error_log
    
  3. CIBAの仕様では、Keycloakから認証リクエストを受け取ったら201を返す仕様なので、201が返ることを確認します。

    curl "http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php" -o /dev/null -w '%{http_code}\n' -s
    

AD(認証デバイス)のURLをKeycloakのAuthentication Channel Providerに設定

  1. Keycloakサーバの環境変数を設定します。

    AUTHENTICATION_ENTITY_URL=http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php
    export AUTHENTICATION_ENTITY_URL
    printenv | grep AUTHENTICATION_ENTITY_URL
    
    • 確認

      /opt/jboss/keycloak/bin/jboss-cli.sh -c
      /subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-auth-channel:read-attribute(name=properties.httpAuthenticationChannelUri)
      exit
      
    • 環境変数を使わず直に指定するのもあり

      /subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-http-auth-channel:add(enabled=true,properties={httpAuthenticationChannelUri="http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php"})

       

Keycloak管理画面上の設定

  1. 任意のレルムを作成します。
    • 手順割愛
  2. 任意のユーザを作成します。
    • 手順割愛
  3. CIBA用のOpenID Connectクライアントを作成します。
    1. レルム > Clients > Createボタンを押下します。
    2. 「Client ID」に任意の名前を入力し、Saveボタンを押下します。
    3. 以下の設定を変更します。
      • Access Type
        • publicconfidential
      • ※CIBAは、仕様によりクライアント認証が必要なため、publicでは利用できません。
      • OIDC CIBA Grant Enabled
        • OFFON
      • Standard Flow Enabled
        • ONOFF
        • ※Standard Flowを利用しないのであれば、これを無効にすることで、CIBAに不要な「redirect_uri」パラメータの設定が不要になります。

CIBAの実行手順

  1. クライアントIDとクライアントシークレットを「:」で連結した文字列をBase64エンコードして、クライアント認証情報を作成します。

    echo -n '<クライアントID>:<クライアントシークレット>' | openssl base64
    
    • クライアントIDの確認場所
      • レルム > Clients > 設定したクライアント > Client ID
    • クライアントシークレットの確認場所
      • レルム > Clients > 設定したクライアント > Credentialsタブ > Secret
  2. クライアント(本記事ではcurl)がKeycloakのバックチャンネル認証エンドポイントにリクエストを出します。

    curl -X POST \
      -H "Content-Type:application/x-www-form-urlencoded" \
      -H "Authorization:Basic <クライアント認証情報>" \
      -d "scope=openid%20email" \
      -d "login_hint=<Keycloakに登録されているユーザ名>" \
      -d "binding_message=hogefuga" \
    'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/ext/ciba/auth' \
    | jq
    
    • リクエストを受け取ったKeycloakは、裏(バックチャネル)で認証デバイスに認可リクエストを出します。
    • PHPのログに出力されていることを確認します。出力されていない場合は、環境に合わせて別のログファイルを確認、PHPのソースコードを修正してください。
      • tail -f /var/log/httpd/error_log
  3. ユーザが認証デバイスで認証し、Keycloakに認可成否を伝えます。(本来は認証デバイスがKeycloakに認可成否のリクエストを出しますが、今回は手動でcurlで出します)

    curl -X POST \
      -H "Content-Type:application/json" \
      -H "Authorization:Bearer <PHPがログに出力したAuthenticateヘッダの内容>" \
      -d \
    '{
      "status":"SUCCEED"
    }' \
    'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/ext/ciba/auth/callback'
    
    • Authorizationヘッダは、最初にクライアントがバックチャンネル認証エンドポイントにリクエストを出したときにPHPがログに出力したものを使います。
    • 上記のcurlでは問答無用で認証成功の「"status":"SUCCEED"」を渡していますが、本来は(プッシュ通知等を利用して)認証デバイス側でユーザに認可してもらってから出すものです。どう認証するかはCIBAでは定義されていません。
  4. トークン認証が通ったか確認する

    curl -X POST \
      -H "Content-Type:application/x-www-form-urlencoded" \
      -H "Authorization:Basic <クライアント認証情報>" \
      -d "grant_type=urn:openid:params:grant-type:ciba" \
      -d "auth_req_id=<最初のcurlリクエストで返却されたauth_req_id>" \
      'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/token'
    
    • 通らなかった時(まだ認証していない時)

      {"error":"authorization_pending","error_description":"The authorization request is still pending as the end-user hasn't yet been authenticated."}
      
    • 通らなかった時(すでに「auth_req_id」の有効期限が切れている時)

      {"error":"invalid_grant","error_description":"Invalid auth_req_id"}
      
    • 通った時 (以下は見やすくするため改行を入れています)

      {
       "access_token": "eyJ〜〜中略〜〜unmDQ",
       "expires_in": 300, "refresh_expires_in": 1800,
       "refresh_token": "eyJ〜〜中略〜〜psW4",
       "token_type": "Bearer",
       "not-before-policy": 0,
       "session_state": "959706e2-1ff5-43a3-8952-0fdf812768f0",
       "scope": "email profile" 
      }
  1. クライアントは、取得したアクセストークン(レスポンス内のaccess_token)を利用して何かを行います。

今後

参考

コメントを追加

プレーンテキスト

  • HTMLタグは利用できません。
  • 行と段落は自動的に折り返されます。
  • ウェブページのアドレスとメールアドレスは自動的にリンクに変換されます。
CAPTCHA
この質問はあなたが人間の訪問者であるかどうかをテストし、自動化されたスパム送信を防ぐためのものです。