身分証とカードキー — 認証方式の地形図
一言でいうと
SIで認証が難しい理由は、認証が難しいからではなく、すでに認証の仕組みがあるからであり、私たちのシステムはその間に割り込まなければなりません。
なぜSIで認証が難しいのか
新規システムを1つ作るときに、認証は難しくありません。ID/パスワードをDBに入れて終わりです。SIで難しい理由は、すでに認証の仕組みがあるからです。
顧客企業には、たいてい次のようなものがすでにあります。
- 人事システム(人の元データ。入社・退社・部署異動はここから始まります)
- Active DirectoryまたはLDAP(PCのログイン、ファイルサーバー、メール)
- グループウェア/ポータル(社内SSOの事実上のゲートウェイ)
- 古いWebSSO製品(SiteMinderのようなもの。15年間動き続けています)
- そして最近追加したKeycloakやクラウドIdP
作るシステムは、この間に入り込む必要があります。そのため、「ログインを付けてください」という一行の要件が、実際には2か月がかりの仕事になります。
混同してはいけない4つ
| 名前 | 何か | ひとことで例えると |
|---|---|---|
| LDAP | ユーザー・組織の情報を保持するディレクトリプロトコル | 電話帳 |
| SAML 2.0 | XMLベースの認証連携(SSO)プロトコル | 紙の身分証 |
| OIDC | OAuth 2.0の上に載せた認証連携プロトコル | デジタル身分証 |
| OAuth 2.0 | 認可(権限委譲)のフレームワーク | ホテルのカードキー |
最も重要な区別は、最後の2つです。
OIDC / SAMLは身分証の発行で、OAuth 2.0はホテルのカードキーです。 カードキーは「このカードで305号室とジムのドアを開けられる」と言うだけで、 所持者が誰なのかは教えてくれません。
ここから有名なアンチパターンが生まれます。アクセストークンでログインを判断すること。アクセストークンには、「誰に発行したのか」の検証根拠(aud)がないか、緩いです。別のアプリ向けに発行されたトークンを持ってきて貼り付けると通ってしまう、トークン置換攻撃が可能です。ログインの判断はIDトークンで行う必要があります。
LDAPは認証プロトコルなのか
半分だけ正解です。LDAPはディレクトリ照会プロトコルで、bind操作でパスワード検証ができます。そのため、「LDAP認証」という言葉が成り立ちます。しかしそれは、アプリケーションがパスワードを直接受け取ってLDAPに問い合わせる方式です。つまりSSOではありません。アプリごとにログイン画面が別々にあります。
実務では、次のように組み合わせます。
[사용자] --로그인--> [IdP(Keycloak 등)] --bind/조회--> [AD / LDAP]
| |
|<---- ID 토큰 --------|
v
[우리 시스템] ← 토큰만 검증. 비밀번호를 절대 보지 않는다
このコードブロックの韓国語は、角括弧の中が順にユーザーと私たちのシステムを表し、コメントは、トークンだけを検証し、パスワードは決して見ない、という意味です。
私たちのシステムがパスワードを見ないことが、核心的な利点です。パスワードを見なければ、漏洩事故の当事者になりません。
レガシーヘッダー認証: 韓国の大企業・金融イントラネットの現実
15年前のWebSSO製品(SiteMinder系)は、次のように動作します。Webサーバーにエージェントを入れ、認証が終わるとHTTPヘッダーにユーザー情報を載せてバックエンドのアプリケーションに渡します。
SM_USER: jdoe
SM_USERDN: uid=jdoe,ou=people,dc=example,dc=com
SM_USERGROUPS: hr-staff^payroll-admin
バックエンドのJavaアプリは、フィルターでrequest.getHeader("SM_USER")を読んでログイン処理をします。簡単で、アプリの修正がほとんどなく、そのため数百のアプリがこの方式で接続されています。
問題は明確です。アプリがヘッダーを無条件に信頼するということは、エージェントを迂回してアプリに直接到達できる経路が1つでもあれば、即座に認証バイパスになるということです。
curl -H "SM_USER: ceo" -H "SM_USERGROUPS: payroll-admin" \
http://hr-app.internal:8080/hr/payroll/list
この1行が通用するシステムが、実際に存在します。そのため、必要な統制が3つあります。
- ネットワーク分離: アプリのポートに、ゲートウェイ以外からはアクセスできないようにします
- エッジでのヘッダー削除: 外部から入ってきた
SM_*ヘッダーを無条件に削除し、新しく設定し直します - ゲートウェイとアプリ間の相互認証: mTLSまたは共有シークレットを使います
そして、これは昔の話ではありません。現代のリバースプロキシ認証(oauth2-proxyなど)も、まったく同じ構造です。ヘッダーで身元を渡すすべての構成に、同じ要求が付いて回ります。
補足として、ヘッダー方式にはパースの落とし穴もあります。SM_USERGROUPSの区切り文字はキャレット(^)です。カンマだと仮定してパースするコードは、グループを1つとして認識してしまい、権限がまるごと消えます。マイグレーションのインベントリを作るときは、ヘッダー名だけでなく値の形式まで調査する必要があります。
何をいつ使うのか
| 状況 | 選択 |
|---|---|
| 新規のWeb/モバイルで、自分たちでIdPを制御できる | OIDC(Authorization Code + PKCE) |
| 相手が大企業/公共機関で、すでにSAMLのIdPがある | SAML 2.0 |
| 社内アカウント情報の照会・グループ判定だけが必要 | LDAP照会(認証はIdPに委譲) |
| サーバー間通信(バッチ、連携) | OAuth 2.0 client_credentials |
| 15年前のアプリ100個をすぐには直せない | プロキシベースのヘッダー注入+上記の統制3種 |
最後の行がSIの現実です。理想的な答えはすべてOIDCですが、アプリを直せない状況で、実際に機能する答えが必要です。そのようなときは、プロキシがSSOを代わりに処理し、アプリにはヘッダーで渡す構成を使います。ただし、上の3つの統制を必ず一緒に設定します。
最後に、バッチとサービスアカウントを忘れてはいけません
SSO移行プロジェクトで、繰り返し起きる事故があります。月末/四半期末にだけ発生する認証失敗です。
原因は、たいてい次のとおりです。夜間バッチや連携プログラムが、人のようにログイン画面を真似して認証を通過していました。人のアカウントだけを移行対象にすると、これを見落とします。そして、月末にだけ動くバッチなので、移行後3週間は何の問題もないまま、ある日起きます。
インベントリ調査に、必ず入れるべき質問があります。
「人ではないもののうち、この認証を通過しているものはありますか」
答えが「ない」なら、十中八九、まだ見つけていないだけです。
現場での姿
「ログインを付けてください」という一行の要件が2か月がかりの仕事になる過程は、たいてい次のように進みます。
まず、どこに接続するかが決まっていません。人事システムが人の元データですが、ログインはADが受け、事実上のゲートウェイはグループウェアで、最近追加したIdPも別にあります。「うちの会社のアカウント」が何を指すのか、担当者ごとに違う答えが返ってきます。
次に、レガシーヘッダー認証に出会います。前段のWebSSO製品がユーザーIDをHTTPヘッダーに載せて渡す方式ですが、15年間動いていて、変えられません。このとき、必ず確認すべきことが1つあります。そのヘッダーを外部から直接入れられるかです。プロキシがヘッダーを上書きしなければ、誰でも他人のIDで入れてしまいます。
最後に、バッチとサービスアカウントが抜け落ちます。人のアカウントだけを設計してサービスインしたあと、夜間バッチがどんな資格で接続するかを、そのときになって決めることになります。