🗂

SAML認証の仕組み解説とAzure環境でのハンズオン

2024/12/03に公開

1. SAML認証とは?

SAML(Security Assertion Markup Language)は、認証(Authentication)と認可(Authorization)を異なるシステム間でセキュアに認証情報をやり取りするためのXMLベースのプロトコルです。
※後ほど記載しているハンズオンの箇所でxmlファイルについても扱っています。

1.1 認証の仕組み

認証と認可とは

  • 認証(Authentication): ユーザーが誰であるかの確認をする(例: ユーザー名とパスワードで本人確認)。
  • 認可(Authorization): 認証されたユーザーがどのような権限を持つかを確認する(例: 管理者の権限であるとか一般ユーザの権限であるとか)。

サービスプロバイダ(SP)とアイデンティティプロバイダ(IdP)の役割

  1. サービスプロバイダ(SP: Service Provider):
  • ユーザーがアクセスするアプリケーションやサービスを提供します。
  • 認証のために、IdPにユーザー認証を委任します。
  • 認証が成功した後にユーザーにサービスを提供します。
    ※後のハンズオンの箇所ではsaml_app.pyがその役割を果たしています。

2.アイデンティティプロバイダ(IdP: Identity Provider):

  • ユーザーの認証情報を管理し、本人確認を行います。
  • 認証結果(SAMLレスポンス)をSPに返却します。
    ※後のハンズオンの箇所ではAzure Entra IDがその役割を果たしています。
  1. 図解でSAMLのフローを説明
    以下は、SAML認証フローのシーケンス図を示したものです。後ほどこちらの図が構築した環境のどこに相当するかを記載しています。
SAML認証フロー図
┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│    ユーザー    │     │      SP       │     │      IdP      │
└───────────────┘     └───────────────┘     └───────────────┘
(1) サービス要求
        │────────────────────>│
(2) リダイレクト指示
        │<────────────────────│
(3) IdPへのリダイレクト要求
        │────────────────────>│
                              │────────────────────>│
(4) ログイン画面表示
                              │<────────────────────│
        │<────────────────────│
(5) 認証情報入力
        │────────────────────>│
                              │────────────────────>│
(6) 認証結果送信(SAMLレスポンス)
                              │<────────────────────│
(7) SPによるSAMLレスポンスの検証 & ユーザーにサービスを提供
        │<────────────────────│

2.SAML認証のハンズオン

2.1 Azureでのリソース作成

Entra IDからエンタープライズアプリケーションをクリック

新しいアプリケーションをクリック

独自のアプリケーション作成をクリック

アプリ名を入力(例: SAML Flask App)して、作成をクリック

作成されたアプリケーションの画面に遷移するので、「1.ユーザーとグループの割り当て」をクリック

SAML認証したいAzureのユーザ(例: admin1)を追加

「2.シングルサインオンの設定」の「作業の開始」をクリック

識別子(エンティティ ID)と応答URLに以下を入力。
識別子: http://localhost:5000/
応答URL: http://localhost:5000/sso/callback

以下の2ファイルをダウンロード
・証明書 (Base64)をダウンロード
→この証明書の中身をのちに記載している「settings.json」内の「x509cert」の箇所に記載します。
・フェデレーション メタデータXMLをダウンロード
→このXMLを後に記載しているプログラム箇所に「metadata.xml」として配置します。

2.2 アプリケーションの作成

以下の構成のアプリケーションを作成します。(ChatGPT先生に聞きながら作りました。)
以下URLにライブラリのサンプルや使用について書かれています。
https://github.com/SAML-Toolkits/python3-saml

  • プログラムの構成
    ファイルの構成が以下になるように各ファイルを作成します。
saml_app
├── saml_app.py               # Flaskアプリケーション本体
├── settings.json             # SAML SPとIdPの基本設定
├── metadata.xml              # IdPまたはSPのメタデータ情報
  1. 必要なライブラリのインストール
    FlaskとSAMLライブラリをインストール
pip install flask python3-saml
  1. プログラムの配置
    saml_app.py
from flask import Flask, request, redirect
from onelogin.saml2.auth import OneLogin_Saml2_Auth
import os

app = Flask(__name__)
BASE_PATH = os.path.dirname(os.path.abspath(__file__))

def prepare_flask_request(request):
    url_data = request.url.split("?")
    return {
        'https': 'on' if request.scheme == 'https' else 'off',
        'http_host': request.host,
        'server_port': request.environ['SERVER_PORT'],
        'script_name': request.path,
        'get_data': request.args.copy(),
        'post_data': request.form.copy(),
    }

@app.route('/')
def index():
    return '<a href="/sso/login">SAMLログイン</a>'

@app.route('/sso/login')
def saml_login():
    req = prepare_flask_request(request)
    auth = OneLogin_Saml2_Auth(req, custom_base_path=BASE_PATH)
    return redirect(auth.login())

@app.route('/sso/callback', methods=['POST'])
def saml_callback():
    req = prepare_flask_request(request)
    auth = OneLogin_Saml2_Auth(req, custom_base_path=BASE_PATH)
    auth.process_response()
    if not auth.is_authenticated():
        return "認証失敗", 401
    return f"ようこそ、{auth.get_nameid()} さん!"

if __name__ == '__main__':
    app.run(debug=True)

settings.json
{tenant-id}の箇所はtenant-idの値を入力してください。

{
    "sp": {
        "entityId": "http://localhost:5000/",
        "assertionConsumerService": {
            "url": "http://localhost:5000/sso/callback",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
        },
        "singleLogoutService": {
            "url": "",
            "binding": ""
        }
    },
    "idp": {
        "entityId": "https://sts.windows.net/{tenant-id}/",
        "singleSignOnService": {
            "url": "https://login.microsoftonline.com/{tenant-id}/saml2",
            "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        },
        "x509cert": "MIIC8DCCAdi~~~HHB"
    }
}

metadata.xml
ダウンロードした「metadata.xml」を配置します。

<?xml version="1.0" encoding="utf-8"?><EntityDescriptor ID=~~~ /></IDPSSODescriptor></EntityDescriptor>

3.実行と動作確認

1 Flaskアプリケーションの起動

 python .\saml_app.py

2 ブラウザでの動作確認
http://127.0.0.1:5000/アクセスし「SAMLログイン」をクリック。

3. Azure Entra IDのログイン画面にリダイレクトされるので、追加した「admin1」認証情報を入力


4.http://localhost:5000/sso/callback に戻り、「ようこそ、[ユーザー名] さん!」と表示される。

※追加していない「user1」でログインしようとしてもエラーになることを確認しています。

以下のようなエラーが返ります。

4.アプリケーションコードとSAML認証フローの紐づけ

2で記載したコードを抜粋し、SAML認証フローとどこのコードが紐づいているのかを記載します。

(1) サービス要求

ユーザーがブラウザでSPのURL(http://127.0.0.1:5000/)にアクセス。
SPは認証状態を確認し、未認証の場合は「SAMLログイン」のリンクを表示。
紐づくコード:

@app.route('/')
def index():
    return '<a href="/sso/login">SAMLログイン</a>'

説明:
/ にアクセスしたユーザーに対し、ログインリンクを返す。

(2) リダイレクト指示

フロー内容: SPがユーザーにIdPへのリダイレクトを指示します。
紐づくコード:

@app.route('/sso/login')
def saml_login():
    req = prepare_flask_request(request)
    auth = OneLogin_Saml2_Auth(req, custom_base_path=BASE_PATH)
    return redirect(auth.login())

説明:
auth.login() がSAMLリクエストを生成し、IdPへのリダイレクトURLを作成します。
ユーザーのブラウザがこのURLを利用してIdPにリクエストを送ります。

(3) IdPへのリダイレクト要求

フロー内容: ユーザーのブラウザがSPのリダイレクト指示に従い、IdPにアクセスします。
紐づく設定:

コードをコピーする
"idp": {
    "singleSignOnService": {
        "url": "`https://login.microsoftonline.com/{tenant-id}/saml2`",
        "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
    }
}

説明:
idp.singleSignOnService.url が、IdPのエンドポイントを指定しています。
SAMLリクエストがこのURLに送信されます。

(4) ログイン画面表示

フロー内容: IdPがログイン画面をユーザーに返します。

(5) 認証情報入力

フロー内容: ユーザーがIdPのログイン画面で認証情報を入力します。

(6) 認証結果送信(SAMLレスポンス)

フロー内容: IdPがSAMLレスポンスを生成し、SPに送信します。
紐づくコード:

"sp": {
    "assertionConsumerService": {
        "url": "http://localhost:5000/sso/callback",
        "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    }
}

この設定に基づき、IdP(Azure Entra ID)が認証結果(SAMLレスポンス)をSPのエンドポイント(/sso/callback)に送信します。

(7) SPによるSAMLレスポンスの検証 & ユーザーにサービスを提供

フロー内容:
SPがSAMLレスポンスを受け取り、検証します。
検証が成功すると、ユーザーにサービスを提供します。
紐づくコード:

@app.route('/sso/callback', methods=['POST'])
def saml_callback():
    req = prepare_flask_request(request)
    auth = OneLogin_Saml2_Auth(req, custom_base_path=BASE_PATH)
    auth.process_response()
    if not auth.is_authenticated():
        return "認証失敗", 401
    return f"ようこそ、{auth.get_nameid()} さん!"

説明:
auth.process_response() がSAMLレスポンスを検証します。
認証成功の場合、auth.get_nameid() でユーザー名を取得して画面に表示します。

5. まとめ

Azure Entra IDを使用してSPとIdP間のSAML認証を簡単に実践できる!!

Discussion