ASP.NET + Sustainsys.Saml2でシングルサインオンを実装する

環境

Sustainsys.Saml2とは

ASP.NET用のSAML2認証ライブラリー saml2.sustainsys.com

サンプルコード

githubからサンプルコードをダウンロードできるので、それをベースに作るのがおすすめ。 github.com SamplesフォルダーにMVC用、Web Forms用、Owin用などが入っている。ライブラリーがプロジェクト参照になっているので、開発する前にNuGetパッケージを参照するように変更したほうがいいと思う。

開発用スタブIdP

公式がWEBで公開している他、前述のサンプルコードにも入っている。

実装

以下はMVC用のサンプルをベースにした例。

やりたいこと

  • IdPは固定で1つだけ。Discovery Serviceは使わない。
  • リクエスト・・・HTTP Redirect Binding
  • レスポンス・・・HTTP POST Binding
  • リクエスト・レスポンス共に署名する。
  • 認証後はユーザー情報をセッションに格納しておく。

設定

Web.configのsustainsys.saml2要素を以下のように変更。

<sustainsys.saml2 entityId="http://localhost:2181/Saml2"
                  returnUrl="http://localhost:2181/Home/Index"
                  authenticateRequestSigningBehavior="Always">
  <identityProviders>
    <add entityId="https://stubidp.sustainsys.com/Metadata"
         signOnUrl="https://stubidp.sustainsys.com/"
         allowUnsolicitedAuthnResponse="true"
         binding="HttpRedirect" wantAuthnRequestsSigned="true">
      <signingCertificate fileName="~/App_Data/stubidp.sustainsys.com.cer"/>
    </add>
  </identityProviders>
  <serviceCertificates>
    <add fileName="~/App_Data/Sustainsys.Saml2.Tests.pfx"/>
  </serviceCertificates>
</sustainsys.saml2>

設定はIdP次第なのであくまで一例として。サンプルとの相違点は以下のとおり。

  • Discovery Serviceを使わないのでdiscoveryServiceUrl属性とfederations要素を削除
  • リクエストに署名するためauthenticateRequestSigningBehavior="Always"追加
  • identityProviderの属性にwantAuthnRequestsSigned="true"追加

ちなみに署名をファイルで指定しているが、本番リリースの際は証明書ストアを使うべき、とのこと。

認証後処理の追加

Global.asax.csを以下のように変更。

using SampleMvcApplication.Models;
using Sustainsys.Saml2.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace SampleMvcApplication
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            Saml2Controller.Options.Notifications.AcsCommandResultCreated = (cr, r) =>
            {
                // クレームからIDを取得
                string userId = cr.Principal.Claims.Single(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;

                // なんらかの方法でユーザー情報を取得
                UserModel userInfo = GetUserInfo(userId);

                // セッションにユーザー情報を格納
                HttpContext.Current.Session["USER_INFO"] = userInfo;
            };
        }
    }
}

Saml2Controller.Options.Notifications.[Notification名]にコールバック関数を設定することで、ライブラリーの中の様々なポイントに処理を追加したりカスタマイズしたりできる。AcsCommandResultCreatedは一連の認証プロセスの最後に発生するので、ユーザー情報の取得とセッションへの格納はここに追加する。

認証時に発生するNotificationの例をいくつか挙げると、

Notification名 発生タイミング・用途
AuthenticationRequestCreated 認証リクエスト作成後に発生。リクエストをカスタマイズしたい場合はここで。
SignInCommandResultCreated IdPへのリダイレクトを返す前に発生
AcsCommandResultCreated ACSの処理が終わった後に発生
ValidateAbsoluteReturnUrl ReturnUrlに絶対パスを指定されたとき発生。パスのOK・NGを判定する関数を設定する。デフォルトだと絶対パスは常にNG。
SelectIdentityProvider 認証に使用するIdPを決定する際に発生。IdPオブジェクトを返す関数を設定すると既定のロジックの代わりにこちらで決定される。

などがある。他にも色々あるので詳細はSaml2Notificationsクラスのソースを参照。

Saml2/Saml2Notifications.cs at master · Sustainsys/Saml2 · GitHub

あとがき

充実したサンプルにスタブIdPとまさに至れり尽くせりなライブラリー。まあ欲を言えば、Notificationの使い方はドキュメントに書いておいてほしかったけど。