コンテンツにスキップ

カスタム OIDC 連携(デプロイメントプラン)


概要

Guanceデプロイメントプランは、カスタム OIDC 方式によるサードパーティの ID プロバイダー(IdP)との連携をサポートしています。以下のユースケースに対応します。

  • 単一の IdP でありながら、OIDC フローまたは返却構造が標準実装と異なる場合。
  • 複数の IdP が併存し、ログインエントリで発信元を区別する必要がある場合。
  • code → token → userinfo の過程で、アドレス生成、アカウント情報の変換、またはアカウントの統合を自前で処理する必要がある場合。

本ドキュメントは「複数 IDP における OIDC 設定説明」をもとに整理し、単一 IDP の利用方法も補足しています。両シナリオは同一のカスタム OIDC 機能を共有し、違いは主にフロントエンドのログインエントリと Func のルーティングロジックにあります。

前提条件

  • Guanceベースバージョンが 1.123.216 以上であること。
  • デプロイメントプランの Launcher 設定権限を有していること。
  • 組み込み Func が有効であり、関数 API を作成可能であること。
  • 対象の IdP から client_idclient_secret、認可エンドポイント、トークンエンドポイント、ユーザー情報エンドポイントなどの情報を取得済みであること。
  • ユーザー情報フィールドと Guanceアカウントフィールドのマッピング関係を明確にしていること。

実装の仕組み

カスタム OIDC 方式の核心は、OIDC の主要フローを Func に委譲することです。

  1. OIDCClientSet.wellKnowURL は、IdP の .well-known/openid-configuration を直接参照せず、Func の well_know 関数を指します。
  2. ログイン時、Guance内部は authorization_endpoint を介して Func の get_auth_url を呼び出し、その関数が最終的なログインアドレスを返します。
  3. コールバック時、Guance内部は userinfo_endpoint を介して Func の get_userinfo を呼び出し、その関数が内部で code → token → userinfo を実行します。
  4. Func は異なる IdP のユーザー情報を正規化した後、直接 Guanceに返し、システムがアカウントのマッチングとログインを処理します。

単一 IDP と複数 IDP の違い

シナリオ フロントエンドのログインエントリ Func のルーティング方法 コールバックアドレス
単一 IDP ログインボタンは 1 つだけ type が未指定の場合はデフォルトの IdP を直接使用 type を省略可能。固定の値を付与しても可
複数 IDP 複数のログインボタン。各ボタンは異なる type を保持 main_oidctype に応じて異なる IdP サブスクリプトに振り分け 対応する IdP のコールバックアドレスと一致させる必要があり、通常は type=xxx を含む

単一 IDP の場合は、メインスクリプトでデフォルト値を直接ハードコードすることを推奨します。例:xtype = args.get("type") or "keycloak"。そうすれば、後で複数 IDP に拡張する際に、フロントエンドエントリと振り分けロジックを追加するだけで済みます。

操作手順

1. forethought-core/coreOIDCClientSet を設定する

Launcher で 名前空間:forethought-core > core に移動し、config.yaml に以下の設定を追加または変更します。

OIDCClientSet:
  # カスタム OIDC を有効化
  enableCustomOIDC: true

  # Func の well_know 関数の API アドレスを指定
  wellKnowURL: "<Func well_know API アドレス>"

  mapping:
    username: preferred_username
    mobile: mobile
    email: email
    exterId: sub

説明:

  • enableCustomOIDC: true はカスタム OIDC を有効にするためのキースイッチです。
  • wellKnowURL は Func の well_know 関数を指す必要があり、IdP のサービスディスカバリアドレスを直接指定してはいけません。
  • mapping 内のフィールド名は、Func が最終的に返すユーザー情報の構造と一致している必要があります。

2. フロントエンドのログインエントリを設定する

Launcher で 名前空間:forethought-webclient > front-web-config に移動し、config.js を変更します。

単一 IDP の例

window.DEPLOYCONFIG = {
  ...
  paasCustomLoginInfo: [
    {
      label: "OIDC ログイン",
      url: "https://<デプロイメントプラン Web ドメイン>/oidc/login",
      desc: "カスタム OIDC ログイン"
    }
  ],
  paasCustomLoginUrl: "https://<IdP ログアウトアドレス>?redirect_url=https://<デプロイメントプラン Web ドメイン>/oidc/login"
}

複数 IDP の例

window.DEPLOYCONFIG = {
  ...
  paasCustomLoginInfo: [
    {
      iconUrl: "https://<アイコンアドレス>",
      label: "Keycloak ログイン",
      url: "https://<デプロイメントプラン Web ドメイン>/oidc/login?type=keycloak",
      desc: "OIDC Keycloak ログイン"
    },
    {
      iconUrl: "https://<アイコンアドレス>",
      label: "Authing ログイン",
      url: "https://<デプロイメントプラン Web ドメイン>/oidc/login?type=authing",
      desc: "OIDC Authing ログイン"
    }
  ]
}

説明:

  • 複数 IDP のシナリオでは、type パラメータで発信元を識別することを推奨します。
  • この type は Func に透過され、メインスクリプトが異なる IdP を区別するために使用します。
  • IdP のコールバックアドレスも type に依存する場合、フロントエンドのログインエントリ、IdP の設定、Func サブスクリプト内の redirect_uri の 3 か所で一致させる必要があります。

補足:SSO ログアウトアドレスの設定

ユーザーが Guanceからログアウトしたときに、サードパーティの認証センターからも同時にログアウトさせたい場合は、front-web-configconfig.jspaasCustomLoginUrl を追加します。

window.DEPLOYCONFIG = {
  ...
  paasCustomLoginInfo: [
    {
      label: "OIDC ログイン",
      url: "https://<デプロイメントプラン Web ドメイン>/oidc/login",
      desc: "カスタム OIDC ログイン"
    }
  ],
  paasCustomLoginUrl: "https://<IdP end_session_endpoint>?redirect_url=https://<デプロイメントプラン Web ドメイン>/oidc/login"
}

説明:

  • paasCustomLoginUrl はサードパーティのログアウトアドレスです。通常、IdP の .well-known/openid-configuration にある end_session_endpoint を参照します。
  • カスタム OIDC 方式では、OIDCClientSet.wellKnowURL は Func の well_know インターフェースを指しますが、paasCustomLoginUrl には最終的なサードパーティ IdP のログアウトアドレスを記入し、Func API アドレスは記入しません。
  • PDF の初期設定例では、サードパーティのログアウトアドレスは redirect_url パラメータを使用して https://<デプロイメントプラン Web ドメイン>/oidc/login にリダイレクトするのが一般的です。
  • お使いの IdP が post_logout_redirect_urireturnTo などの別のパラメータ名を使用している場合は、その IdP の実際のプロトコル要件に従ってください。
  • paasCustomLoginUrl は 1 つの設定値しか持てないため、複数 IDP のシナリオでは、例えば中間ログアウトアドレスを用意して、そこからコンテキストに応じて対応する IdP にリダイレクトするなど、統一的なログアウト戦略を別途設計する必要があります。
  • 未ログイン状態でサイトにアクセスしたときに直接サードパーティ認証を起動したい場合は、paasCustomLoginUrl/oidc/login に直接設定することもできます。

3. Web Nginx の転送ルールを設定する

名前空間:forethought-webclient > front-web-confignginx.conf を変更し、OIDC ログインとコールバックのリクエストを inner サービスに転送します。

location /oidc/login {
    proxy_connect_timeout 5;
    proxy_send_timeout 5;
    proxy_read_timeout 300;
    proxy_http_version 1.1;
    proxy_set_header Connection "keep-alive";
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    proxy_pass http://inner.forethought-core:5000/api/v1/inner/oidc/login;
}

location /oidc/callback {
    proxy_connect_timeout 5;
    proxy_send_timeout 5;
    proxy_read_timeout 300;
    proxy_http_version 1.1;
    proxy_set_header Connection "keep-alive";
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers X-Requested-With;
    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
    proxy_pass http://inner.forethought-core:5000/api/v1/inner/oidc/callback;
}

この手順の目的は、ブラウザがアクセスする /oidc/login/oidc/callback を最終的にデプロイメントプラン内部の OIDC 処理ロジックに到達させることです。

4. Func でメインスクリプトを作成する

メインスクリプトは外部に統一エントリを提供し、type に応じて異なる IdP のサブスクリプトに振り分けます。少なくとも以下の 3 つの関数を含めることを推奨します。

  • well_knowOIDCClientSet.wellKnowURL に使用するサービスディスカバリ情報を返します。
  • get_auth_url:最終的に IdP にリダイレクトする認証アドレスを生成して返します。
  • get_userinfo:コールバックパラメータを受け取り、内部で code → token → userinfo を実行します。

例:

import __keycloak as keycloak_client
import __authing as authing_client

@DFF.API('OIDC サービスディスカバリインターフェース')
def well_know():
    return {
        "authorization_endpoint": "<Func get_auth_url API アドレス>",
        "token_endpoint": "",
        "userinfo_endpoint": "<Func get_userinfo API アドレス>"
    }

@DFF.API('ログインアドレス情報を取得')
def get_auth_url(**kwargs):
    args = kwargs.get("args", {})
    xtype = args.get("type") or "keycloak"

    if xtype == "keycloak":
        return keycloak_client.get_auth_url(**kwargs)
    elif xtype == "authing":
        return authing_client.get_auth_url(**kwargs)
    else:
        raise Exception(f"不正な値 type=`{xtype}`")

@DFF.API('ユーザー情報取得インターフェース')
def get_userinfo(**kwargs):
    args = kwargs.get("args", {})
    xtype = args.get("type") or "keycloak"

    if xtype == "keycloak":
        return keycloak_client.get_userinfo(**kwargs)
    elif xtype == "authing":
        return authing_client.get_userinfo(**kwargs)
    else:
        raise Exception(f"不正な値 type=`{xtype}`")

説明:

  • well_know が返す authorization_endpointuserinfo_endpoint は、実際には Func API のアドレスです。
  • token_endpoint はこの方式では通常空欄で構いません。code → token の処理は get_userinfo 内部で行われるためです。
  • 単一 IDP のシナリオでも、このメインスクリプトの層を残しておくことを推奨します。後で拡張しやすくなります。

5. IdP ごとにサブスクリプトを作成する

各 IdP のサブスクリプトは個別に管理することを推奨します。例:__keycloak.py__authing.py。サブスクリプトには少なくとも以下の内容を実装する必要があります。

  • OIDC_SET:現在の IdP のクライアント設定。
  • well_know:現在の IdP の実際の OIDC エンドポイント。
  • get_auth_url:認証アドレスを生成。
  • turn_token:コールバックの code からトークンを取得。
  • turn_userinfo:トークンからユーザー情報を取得。
  • get_userinfo:外部向けの統一エントリ。内部で turn_tokenturn_userinfo を順に呼び出します。

構造例:

OIDC_SET = {
    "client_id": "<OIDC クライアント ID>",
    "client_secret": "<OIDC クライアントシークレット>",
    "scope": "openid profile email address",
    "redirect_uri": "https://<デプロイメントプラン Web ドメイン>/oidc/callback?type=authing",
    "grant_type": "authorization_code"
}

def well_know():
    return {
        "authorization_endpoint": "https://<IdP 認可アドレス>",
        "token_endpoint": "https://<IdP トークンアドレス>",
        "userinfo_endpoint": "https://<IdP ユーザー情報アドレス>",
        "end_session_endpoint": "https://<IdP ログアウトアドレス>",
        "jwks_uri": "https://<IdP JWKS アドレス>"
    }

!!! warning

上記の `OIDC_SET` は設定構造の説明のみを目的としており、Func スクリプト内で `client_secret`、トークン、その他の機密資格情報を直接ハードコードすることは推奨しません。

鍵情報を管理する必要がある場合は、まず Func 側のパスワード型環境変数を使用して注入および読み取りを行ってください。特定の連携シナリオで依然として `OIDCClientSet.clientSecret` を使用する必要がある場合も、同様の方法で安全に管理し、平文でスクリプトや設定例に記述しないようにしてください。

重要な注意点:

  • 複数 IDP のシナリオでは、redirect_uri に対応する type=xxx パラメータを必ず含めてください。
  • IdP 側に登録するコールバックアドレスは、OIDC_SET.redirect_uri と完全に一致している必要があります。
  • get_auth_url の戻り値は {"url": "..."} の固定形式です。
  • get_userinfo の戻り値はアカウント情報の JSON であり、ユーザー属性は第一階層の構造に直接配置することを推奨します。

SSO ログアウトの説明

PDF「001-デプロイメントプラン【OIDC】サードパーティ認証ログイン設定」の説明に従い、Guanceが現在サポートしている SSO ログアウト方式は以下のとおりです。

  1. ユーザーが Guance側でログアウトをクリックします。
  2. システムがまずローカルセッションを破棄します。
  3. ローカルセッションの破棄が完了した後、ブラウザが paasCustomLoginUrl で指定されたサードパーティのログアウトアドレスにリダイレクトします。
  4. サードパーティの認証センターがログアウトを完了した後、そのパラメータルールに従ってログインページまたは指定されたページにリダイレクトします。

制限事項:

  • 現在のシステムは、サードパーティの認証センターから能動的に発行されるログアウト要求を処理できません。
  • 言い換えれば、Guanceが能動的にトリガーするログアウトフローのみをサポートし、IdP がシングルログアウトを発行してから Guanceにコールバックして連動ログアウトを完了する方式はサポートしていません。
  • そのため、連携段階では、「誰がログアウトを発行するのか」「ログアウト後にどこに戻るのか」という 2 点を事前にクライアントと確認することを推奨します。

6. ユーザー情報フィールドを統一する

Func が最終的に Guanceに返すユーザー情報は、OIDCClientSet.mapping と一対一で対応する必要があります。少なくとも以下のフィールドが存在することを確認してください。

Guanceアカウントフィールド IdP フィールド例
username preferred_username
email email
mobile mobile
exterId sub

複数の IdP で同一人物が同じメールアドレスを使用している場合、Func 内で統一して同じアカウント識別子として処理することを推奨します。例:

  • email を統一して企業メールアドレスに設定します。
  • sub または exterId を統一してメールアドレス値に設定します。
  • 異なる IdP で返却構造が一致しない場合は、まず Func 内で統一 JSON に変換してから返却します。

この手順は、複数 IdP ログイン時に重複アカウントが生成されるのを防ぐための鍵です。

7. 必要に応じて過去の OIDC アカウントをクリーンアップする

環境に既存の OIDC アカウントが存在し、異なる IdP によって同じメールアドレスで複数のアカウントが生成されている場合は、以下の方法で対応できます。

-- OIDC アカウントに同じメールアドレスの重複アカウントが存在するか確認
select email, count(id) as num
from `main_account`
where status = 0 and `exterId` <> ""
group by email
having num > 1;

-- 不要なアカウントをクリーンアップした後、exterId を email に統一
update `main_account`
set exterId = email
where status = 0 and `exterId` <> "";

実行前に必ずバックアップを取得し、実際のアカウント状態を確認した上で慎重に処理してください。

デバッグに関する推奨事項

  • 結合テストの初期段階では、get_userinfo の最後にアカウント情報を出力し、一時的に例外を発生させて、まずフィールドが期待通りかを確認してからログインを許可してください。
  • 重複アカウントが発生した場合は、まず異なる IdP が返す emailsubexterId が統一されているかを確認してください。
  • コールバック後にログインが失敗する場合は、まず redirect_uri が IdP 側に登録した値と完全に一致しているかを確認してください。
  • 単一 IDP から複数 IDP に変更する場合は、まずデフォルトの type を残し、徐々に新しいログインエントリとサブスクリプトを追加してください。

よくある質問

1. なぜ wellKnowURL に IdP の .well-known アドレスを直接指定できないのですか?

この方式の目的は、標準の OIDC エンドポイントを読み取るだけでなく、ログインアドレスの生成、コールバック処理、アカウントの正規化といったロジックを Func に委ねることにあるため、wellKnowURL はカスタムの well_know 関数を指す必要があります。

2. 単一 IDP でもこの方式を推奨する理由は何ですか?

多くの単一 IDP シナリオは、実質的に「非標準 OIDC」連携だからです。例えば、ログインアドレス、コールバックパラメータ、ユーザー情報構造、アカウント主キーなどを変更する必要がある場合があります。この方式を使用すると、これらの互換性問題を統一的に処理できます。

3. IdP が返すユーザー情報の構造が深い階層になっている場合はどうすればよいですか?

Func 内で解析してフラット化し、最終結果を第一階層の構造に整理してから Guanceに返すことを推奨します。複雑なネスト構造をそのままシステムのアカウントマッピングに渡さないでください。

フィードバック

このページは役に立ちましたか?