Salesforce SSO徹底解説:セキュアでシームレスなID連携を実現するためのアーキテクトガイド

背景と応用シナリオ

現代のエンタープライズ環境では、従業員、パートナー、顧客が業務を遂行するために多数のアプリケーションを利用しています。Salesforceもその中核をなすプラットフォームの一つですが、各システムが独自の認証情報(ユーザー名とパスワード)を要求する場合、いくつかの重大な課題が生じます。パスワードの記憶と管理の負担が増大し、ユーザーエクスペリエンスが低下するだけでなく、パスワードの使い回しや脆弱なパスワードの設定といったセキュリティリスクも高まります。また、情報システム部門にとっては、ユーザーの入退社に伴うアカウントの作成・棚卸し・削除といったプロビジョニング管理が煩雑化し、管理コストの増大とセキュリティガバナンスの低下を招きます。

これらの課題を解決するためのアーキテクチャ上の解決策が、Single Sign-On (SSO)(シングルサインオン)です。SSOは、ユーザーが一度の認証プロセスを通過するだけで、連携された複数のアプリケーションやサービスにアクセスできるようにする仕組みです。SalesforceアーキテクトとしてSSOを設計・導入することで、以下のような価値を実現できます。

応用シナリオ:

  • 従業員の生産性向上 (B2E): 従業員が自社のコーポレートID(例:Microsoft Azure Active DirectoryやOktaの認証情報)を使用してSalesforceにログインできるようにします。これにより、従業員はパスワードを覚える必要がなくなり、シームレスに業務を開始できます。
  • パートナーとの連携強化 (B2B): パートナー企業の担当者が、自社の認証システムを使ってSalesforceのパートナーコミュニティ(Experience Cloud)にアクセスできるようにします。これにより、セキュアな情報共有とコラボレーションを促進します。
  • 顧客エンゲージメントの向上 (B2C): 顧客がGoogle、Facebook、LINEなどのソーシャルアカウントを利用して、カスタマーポータル(Experience Cloud)に簡単にログインできるようにします。これにより、登録のハードルを下げ、サービスの利用を促進します。

SSOは単なる利便性向上ツールではなく、企業のID管理戦略とセキュリティ体制の根幹をなす重要なアーキテクチャコンポーネントなのです。


原理説明

Salesforce SSOをアーキテクトの視点から理解するには、その中核をなす登場人物とプロトコルを把握することが不可欠です。SSOの連携は、主に二つの役割によって構成されます。

  • Identity Provider (IdP)(IDプロバイダー): ユーザーの認証を実際に行い、本人確認の責任を負うシステムです。IdPはユーザー情報を一元的に管理し、認証が成功すると、ユーザーに関する情報(アサーション)を発行します。代表的なIdPには、Microsoft Azure AD, Okta, PingFederate, Google Workspaceなどがあります。
  • Service Provider (SP)(サービスプロバイダー): ユーザーがアクセスしようとしているアプリケーションやサービスです。今回の文脈では、SalesforceがSPの役割を担います。SPはIdPを信頼し、IdPが発行したアサーションを受け取ってユーザーのログインを許可します。

このIdPとSP間の信頼関係を確立し、安全に認証情報を交換するために、標準化されたプロトコルが使用されます。Salesforceでは主に以下の二つがサポートされています。

SAML (Security Assertion Markup Language)

SAML(セキュリティ アサーション マークアップ言語)は、XMLベースの標準プロトコルで、主にエンタープライズ向けのSSOで広く利用されています。SAMLの認証フローは以下のようになります(SP-Initiated Flowの場合):

  1. ユーザーがSalesforce (SP) にアクセスします。
  2. Salesforceはユーザーが未認証であると判断し、SAML認証要求を生成してユーザーのブラウザを介してIdPにリダイレクトします。
  3. ユーザーはIdPのログイン画面で認証情報(ID/パスワード、MFAなど)を入力します。
  4. IdPは認証に成功すると、ユーザーのID情報(ユーザー名、メールアドレス、プロファイルなど)を含むSAMLアサーション(XML形式の署名付き文書)を生成します。
  5. IdPはSAMLアサーションをユーザーのブラウザに返し、ブラウザはそれをSalesforceのAssertion Consumer Service (ACS) URLにPOSTします。
  6. Salesforceは受け取ったアサーションのデジタル署名を検証し、信頼できるIdPから発行されたものであることを確認します。
  7. 検証が成功すると、Salesforceはアサーション内のFederation ID(フェデレーションID)などを用いて該当ユーザーを特定し、セッションを確立してログインを許可します。

SAMLは堅牢で成熟したプロトコルであり、詳細な属性マッピングや暗号化に対応しているため、B2EやB2Bのシナリオに最適です。

OpenID Connect (OIDC)

OpenID Connect (OIDC)は、OAuth 2.0プロトコルを拡張した、よりモダンな認証プロトコルです。REST/JSONをベースとしており、モバイルアプリケーションやWebアプリケーションとの親和性が高いのが特徴です。主にB2Cシナリオやソーシャルログインで利用されます。

OIDCのフローは、IdP(OIDCではAuthorization Serverとも呼ばれる)からIDトークン(JWT形式)を取得することが中心となります。このIDトークンには、認証されたユーザーの情報が含まれており、SP(OIDCではRelying Party)はこれを検証してユーザーをログインさせます。

SAMLと比較して、OIDCは軽量で実装が容易なため、特にWeb APIを多用するモダンなアーキテクチャに適しています。

Just-in-Time (JIT) Provisioning

Just-in-Time (JIT) Provisioning(ジャストインタイムプロビジョニング)は、SSOのアーキテクチャにおいて極めて重要な機能です。JITを有効にすると、ユーザーが初めてSSOでSalesforceにログインした際に、IdPから送られてくるSAMLアサーションやOIDCのIDトークン内の情報に基づいて、Salesforce上にユーザーアカウントを自動的に作成または更新できます。

これにより、管理者が事前にSalesforceにユーザーを手動で作成しておく手間が省け、ユーザー管理をIdPに一元化できます。JITのロジックは、Apexクラスを実装することで高度にカスタマイズ可能であり、アサーション内の属性に応じてプロファイルやロールを動的に割り当てるといった複雑な要件にも対応できます。


サンプルコード

SAML JIT Provisioningのロジックをカスタマイズする場合、Apexでハンドラクラスを作成します。このクラスは `Auth.SamlJitHandler` インターフェースを実装する必要があります。以下は、Salesforceの公式ドキュメントに基づくサンプルコードです。このコードは、SAMLアサーション内の属性を使用してユーザーを検索し、存在しない場合は新しいユーザーを作成します。さらに、特定の属性に基づいてプロファイルやロールを割り当てるロジックも含まれています。

SAML JIT Handler Apexクラスの例

// SAML JIT Handlerのカスタム実装
// このクラスは、ユーザーがSSOで初めてログインした際に呼び出され、
// ユーザーの作成または更新を処理します。
global class SamlJitHandler implements Auth.SamlJitHandler {

    // ユーザー作成時のデリゲートアカウント
    // このアカウントの所有するレコードが、新規作成ユーザーに移行されることがあります。
    private class JitUserProvisioningDelegate {
        private Id communityId;
        private Id accountId;
        private Id profileId;
        private String role;
        
        JitUserProvisioningDelegate(Id commId, Id acctId, Id profId, String r) {
            communityId = commId;
            accountId = acctId;
            profileId = profId;
            role = r;
        }
    }

    // ユーザー作成を処理するメインメソッド
    private User createUser(String federationIdentifier, Map<String, String> attributes,
        String assertion, JitUserProvisioningDelegate delegate) {
        
        // 新しいユーザーオブジェクトをインスタンス化
        User u = new User();
        u.FederationIdentifier = federationIdentifier;

        // SAMLアサーションから受け取った属性をユーザー項目にマッピング
        u.Email = attributes.get('email');
        u.LastName = attributes.get('lastName');
        u.FirstName = attributes.get('firstName');
        u.Alias = attributes.get('firstName') + attributes.get('lastName').substring(0,1);
        String username = attributes.get('email');
        u.Username = username;
        u.CommunityNickname = username.substring(0, username.indexOf('@'));
        u.ProfileId = delegate.profileId;
        u.LanguageLocaleKey = 'ja';
        u.LocaleSidKey = 'ja_JP';
        u.TimeZoneSidKey = 'Asia/Tokyo';
        u.EmailEncodingKey = 'ISO-2022-JP';
        
        // Experience Cloudサイトのユーザーであれば、取引先と取引先責任者を設定
        if (delegate.accountId != null) {
            Contact c = new Contact();
            c.AccountId = delegate.accountId;
            c.LastName = u.LastName;
            c.FirstName = u.FirstName;
            c.Email = u.Email;
            insert c;
            u.ContactId = c.Id;
        }
        
        return u;
    }

    // ユーザー情報を更新するメソッド
    private void updateUser(User u, String federationIdentifier, Map<String, String> attributes,
        String assertion, JitUserProvisioningDelegate delegate) {

        // 必要に応じてユーザー情報を更新するロジックをここに記述
        u.Email = attributes.get('email');
        u.LastName = attributes.get('lastName');
        u.FirstName = attributes.get('firstName');
        
        // Experience Cloudサイトのユーザーであれば、プロファイルとロールを更新
        if (delegate.communityId != null) {
            u.ProfileId = delegate.profileId;
            // ロールも更新
        }
    }
    
    // SamlJitHandlerインターフェースのハンドルメソッドを実装
    global User createUser(Id samlSsoProviderId, Id communityId, Id portalId,
        String federationIdentifier, Map<String, String> attributes, String assertion) {
        
        // ユーザーが存在するかどうかを確認
        User u = [SELECT Id, FederationIdentifier FROM User WHERE FederationIdentifier = :federationIdentifier];
        
        // どのコミュニティにアクセスしているかに基づいてデリゲートをセットアップ
        // この部分は実際の要件に応じてカスタマイズが必要
        Id accountId;
        Id profileId;
        String role;
        
        if (communityId != null) {
            // 例: 特定の取引先とプロファイルを割り当て
            accountId = [SELECT Id FROM Account WHERE Name = 'Default Community Account' LIMIT 1].Id;
            profileId = [SELECT Id FROM Profile WHERE Name = 'Customer Community Plus User' LIMIT 1].Id;
        }
        
        JitUserProvisioningDelegate delegate = new JitUserProvisioningDelegate(communityId, accountId, profileId, role);
        
        if (u == null) {
            // ユーザーが存在しない場合、新しいユーザーを作成
            return createUser(federationIdentifier, attributes, assertion, delegate);
        } else {
            // ユーザーが存在する場合、情報を更新
            updateUser(u, federationIdentifier, attributes, assertion, delegate);
            return u;
        }
    }
    
    // SamlJitHandlerインターフェースの更新メソッドを実装
    global void updateUser(Id userId, Id samlSsoProviderId, Id communityId, Id portalId,
        String federationIdentifier, Map<String, String> attributes, String assertion) {
        
        User u = [SELECT Id FROM User WHERE Id = :userId];
        
        // ユーザー作成時と同様のロジックでデリゲートをセットアップ
        Id accountId;
        Id profileId;
        String role;
        
        if (communityId != null) {
            accountId = [SELECT Id FROM Account WHERE Name = 'Default Community Account' LIMIT 1].Id;
            profileId = [SELECT Id FROM Profile WHERE Name = 'Customer Community Plus User' LIMIT 1].Id;
        }
        
        JitUserProvisioningDelegate delegate = new JitUserProvisioningDelegate(communityId, accountId, profileId, role);
        updateUser(u, federationIdentifier, attributes, assertion, delegate);
    }
}

このコードは、`createUser`と`updateUser`という2つの主要なメソッドを実装しています。`handleJitRequest`が呼び出された際に、Federation IDを基にユーザーを検索し、存在しなければ`createUser`を、存在すれば`updateUser`を呼び出すという流れです。アーキテクトとしては、このハンドラ内でビジネスロジック(どのプロファイルやロールを割り当てるか、どの取引先に紐付けるかなど)をどのように設計するかが腕の見せ所となります。


注意事項

SSOアーキテクチャを設計・導入する際には、いくつかの重要な点に注意する必要があります。

IDマッピング戦略

Federation IDは、IdPとSalesforce間でユーザーを一意に識別するための最も重要なキーです。従業員番号やメールアドレスなど、全社で一意かつ不変の値をFederation IDとして使用することを強く推奨します。Salesforceのユーザー名(Username)は全組織で一意である必要があるため、マッピングキーとして使用すると、将来的に別組織との統合などで問題が発生する可能性があります。

セキュリティと証明書管理

SAMLでは、アサーションの信頼性を担保するためにデジタル署名が使用されます。この署名に使われる証明書には有効期限があります。証明書の有効期限が切れるとSSOが機能しなくなるため、期限を監視し、計画的に更新するプロセスを確立する必要があります。また、セキュリティを強化するために、IdP側で多要素認証 (MFA) を強制し、Salesforceへのアクセス経路をIdP経由に限定する(Salesforceへの直接ログインを無効化する)ことがベストプラクティスです。

プロビジョニングとデプロビジョニング

JITはユーザーの「作成」と「更新」には非常に有効ですが、「無効化(非アクティブ化)」は処理しません。従業員が退職した場合など、IdP側でアカウントが無効化されても、Salesforce上のアカウントはアクティブなまま残ってしまいます。これはセキュリティ上のリスクとなるため、別途デプロビジョニングのプロセスを設計する必要があります。SCIM (System for Cross-domain Identity Management) プロトコルをサポートするIdPを利用するか、API連携による定期的なバッチ処理でユーザーを非アクティブ化する仕組みを構築することが推奨されます。

権限

SSOの設定には、「アプリケーションのカスタマイズ」および「シングルサインオン設定の管理」権限が必要です。また、JITハンドラのApexクラスを作成・編集するには、「Apexの作成」権限が求められます。

エラーハンドリングとテスト

SAMLアサーションのエラー(属性名の不一致、証明書の不一致など)は、SSO失敗の一般的な原因です。Salesforceの「SAMLアサーション検証」ツールを活用して、IdPから送られてくるアサーションの内容をデバッグします。また、本番環境に展開する前に、必ずFull Sandboxなどのテスト環境でSSOのフロー、JITのロジック、ディープリンク(RelayState)の動作を徹底的にテストしてください。


まとめとベストプラクティス

Single Sign-Onは、現代のエンタープライズアーキテクチャにおいて、ユーザーエクスペリエンス、生産性、そしてセキュリティを同時に向上させるための不可欠な技術です。SalesforceアーキテクトとしてSSOを成功に導くためには、技術的な実装だけでなく、組織のID管理戦略全体を見据えた設計が求められます。

ベストプラクティス:

  1. 適切なプロトコルの選択: 社内システム連携(B2E)や企業間連携(B2B)では実績のあるSAMLを、ソーシャルログイン(B2C)やモダンなAPI連携ではOIDCを選択するなど、ユースケースに応じて最適なプロトコルを選定します。
  2. Federation IDを正しく使う: ユーザーマッピングのキーには、必ず不変で一意なFederation IDを使用します。安易にユーザー名やメールアドレスに頼るべきではありません。
  3. デプロビジョニング戦略を確立する: JITに加えて、ユーザーを非アクティブ化するためのプロセス(SCIMやAPI連携)を必ず設計に含めます。IDライフサイクル管理の「出口」を設計することがセキュリティの鍵です。
  4. 認証を一元化する: MFAなどの強力な認証ポリシーは、IdP側で一元的に管理・強制します。これにより、すべての連携アプリケーションで一貫したセキュリティレベルを担保できます。
  5. 緊急アクセス手段を確保する: SSOシステムに障害が発生した場合に備え、特定のシステム管理者用にSSOをバイパスしてログインできる手段(例:`/?login`を付与したURL)を確保し、そのアクセス管理を徹底します。
  6. 徹底したテストとドキュメント化: Sandbox環境での綿密なテストはもちろんのこと、IdPのエンドポイント、証明書の有効期限、属性マッピング、JITハンドラの仕様など、設定内容を詳細にドキュメント化し、運用チームが参照できるようにします。

これらのベストプラクティスに従い、戦略的にSSOアーキテクチャを設計・実装することで、Salesforceプラットフォームの価値を最大限に引き出し、セキュアでスケーラブルなIT基盤を構築することができるでしょう。

コメント