54
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ruby-saml を使ったOneLogin での SAML 認証の処理シーケンスを追う

Last updated at Posted at 2018-06-02

OneLoginが提供しているruby-samlを使ったSAML認証について調査する機会があったので、備忘も兼ねて動きをまとめようと思います。

目的

  • SAML 認証時の処理シーケンスの流れを、ruby-samlのサンプルアプリを動かしながら追いつつ理解すること

想定読者

  • SAML認証を触り始めて間もなく、概念をざっと確認した程度の人
  • 基礎的なRailsアプリを開発したことがある人

前提

今回はOneLoginを使ったSAML認証を確認します。

またOneLoginが提供しているruby-samlを使って、開発しているアプリにSAML認証の処理の流れを確認します。

今回は、こちらもOneLoginが提供しているruby-saml-exampleというSAML認証確認用のサンプルアプリを使って検証していきます。

SAML認証に関する用語のおさらい

簡単に用語を最低限確認しておきます。

用語 意味
Security Assertion Markup Language(SAML) 異なるドメイン間で認証・認可データをやり取りするための標準策定されたXMLベースのマークアップ言語
Identity Provider(IdP) 認証を行い、認証情報を提供する
Service Provider (SP) IdPに認証を委託し、IdPの認証情報を信頼してサービス提供する
Assertion IdPから発行される認証情報。SAML形式で発行される
Assertion Consumer Service (ACS) SPでIdPから発行されたAssertionを解釈し、認可を行う

SAML認証の処理シーケンスの概要図

こちらのSAML認証の処理シーケンスの概要図を前提に、 ruby-saml-example の動きを追っていきます。
図のクラウドサービスは、 ruby-saml-example と読み替えてください。

画像引用元:SAMLとは |クラウド型シングルサインオン・アクセスコントロール(IDaaS) OneLogin - サイバネット

今回の検証では、それぞれ下記が対応します。

検証の準備

下記の記事がわかりやすいので、こちらを参考に検証の準備をします。ここでは詳細は割愛しますが、準備のアウトラインだけ書いておきます。

SSO を実現するための SAML2.0 の実装。まずはサンプルを動かす

IdPの準備

  • トライアル用の OneLogin アカウントを取得する
  • IdPとしてOneLoginにOneLogin SAML Test Connectorを作成する
  • OneLogin SAML Test ConnectorのConfigulationで下記の通り設定する
変更箇所 値の取得場所
Audience http://localhost:3000/saml/metadata
Recipient http://localhost:3000/saml/acs
ACS URL Validator ^http:¥/¥/localhost:3000¥/saml¥/acs$
ACS URL http://localhost:3000/saml/acs

SPの準備

  • ruby-saml-exampleをクローンし、http://localhost:3000 でアプリが起動できるようにしておく
  • OneLogin SAML Test Connector のApp IdとIdPの証明書をアプリに設定する

処理の流れ

それではシーケンスに沿って ruby-saml-example アプリ内でどんな処理をしているか追っていきます。

① ユーザーがSPにアクセス

Webブラウザからhttp://localhost:3000とアクセスすると、「Login」 とシンプルに表示されたページが表示されます。

image.png

② SPはユーザーのリクエストを確認し、SAML認証要求を作成 / ③ SAML認証のリクエストとともにIdPにリダイレクト

「Login」をクリックすると、設定した OneLoginの認証画面にリダイレクトされます。

image.png

図の②、③を担っているのは、SamlController#ssoアクションです。ざっくり何をやっているか確認するとこんな感じです。

  • settings = Account.get_saml_settings(get_url_base)でOneLoginとの連携に関する各種設定を行う
  • request = OneLogin::RubySaml::Authrequest.new でOneLoginへの認証リクエストを作成する
  • redirect_to(request.create(settings)) で、settings の情報を基にOneLoginの認証画面へリダイレクトする

④ OneLoginでSAML認証リクエストを解析・認証 / ⑤ OneLoginの認証画面でユーザー認証

SPからのSAML認証リクエストに応答する形で、OneLogin の認証画面が表示されます。
OneLogin の認証画面で ID/PASS を入力し、ログインします。ログイン処理は OneLogin 側で行います。

image.png

⑥ OneLoginがSAML認証レスポンスを作成 / ⑦ SAML認証レスポンスとともにSPにリダイレクト

OneLoginでのログインが成功すると、OneLoginからSPのACS のURLにリダイレクトされます。

  • SAML認証のレスポンスは Parameters: {"SAMLResponse"=>"XXXXXXXXXXXXXXXXXXXX..."} のように暗号化された状態でリダイレクト先に送信されます。
  • リダイレクト先は、OneLogin SAML Test Connector で指定したACS URLです。今回の場合はhttp://localhost:3000/saml/acsです。

image.png

⑧ SPがSAML認証レスポンスを検証し、ユーザーログインを許可

ACSでSAML認証レスポンスを解析・認可の判断をします。

図の⑧を担っているのは、SamlController#acsアクションです。コードを抜粋します。

  def acs
    settings = Account.get_saml_settings(get_url_base)
    response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => settings)


    if response.is_valid?
      session[:nameid] = response.nameid
      session[:attributes] = response.attributes
      @attrs = session[:attributes]
      logger.info "Sucessfully logged"
      logger.info "NAMEID: #{response.nameid}"
      render :action => :index
    else
      logger.info "Response Invalid. Errors: #{response.errors}"
      @errors = response.errors
      render :action => :fail
    end
  end

response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => settings) でSAML認証のレスポンスを解析してます。
解析されたレスポンスは、この行の下に```logger.debug response.document


参考:<a href="https://www.samltool.com/generic_sso_res.php" target="_blank" rel="nofollow noopener">SAML Response Examples - SAML Assertion Example | SAMLTool.com</a>


saml:Issuerhttps://app.onelogin.com/saml/metadata/xxxxxx
samlp:Status



saml:Issuerhttps://app.onelogin.com/saml/metadata/xxxxxx

ds:SignedInfo



ds:Transforms




ds:DigestValueXXXXXXXXXXXXXXXXXXXXXXXXXXX


ds:SignatureValueXXX
ds:KeyInfo
ds:X509Data
ds:X509CertificateXXX



saml:Subject
example@example.com





saml:AudienceRestriction
saml:Audiencehttp://localhost:3000/saml/metadata



saml:AuthnContext
saml:AuthnContextClassRefurn:oasis:names:flag_tc:SAML:2.0:flag_ac:classes:PasswordProtectedTransport




この認証情報を```response.is_valid?``` でバリデーションチェックし、問題なければユーザーにログインを許可します。

### ⑨ ユーザーがSPにログインできる

ACSのチェックでログインが許可されれば、ログイン完了後の画面に無事遷移します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/9f6f014e-7bde-6c05-230c-90aecc7737d1.png)

## 検証でハマった点

これでログインするまでの一通りの流れは追えたのですが、検証中ハマった点をまとめておきます。

### ```response.is_valid?``` のバリデーションエラーでハマった

検証の最初の方で、OneLogin SAML Test Connectorの ```Audience``` と ```Recipient``` を空欄のままにしていたのですが、そうすると```http://localhost:3000/saml/acs``` でやっている```response.is_valid?``` でバリデーションエラーが出ました。

#### ```Audience```を空にした場合のバリデーションエラー

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/8457907e-fb8e-74ef-1385-eaf8fd8423fb.png)

http://localhost:3000/saml/metadata is not a valid audience for this Response - Valid audiences: {audience}


#### ```Recipient``` を空にした場合のバリデーションエラー

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/c7f850e9-3830-780f-f249-26858ab656cb.png)

The response was received at {recipient} instead of http://localhost:3000/saml/acs


#### バリデーションエラーの原因

ruby-saml の ```is_valid?``` で実施しているバリデーションでコケてしまっているのが原因でした。

https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/response.rb#L346-L383

##### ```Audience```が空の場合

バリデーション```validate_audience```で、```Audience```が ```settings.issuer``` と合ってないぞ!と怒られている

https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/response.rb#L586-L601

空の場合はSAML認証レスポンスの ```Audience``` に ```{audience}```という値が入ってくる

設定した場合

saml:Audiencehttp://localhost:3000/saml/metadata

空の場合

saml:Audience{audience}


##### ```Recipient```が空の場合

バリデーション```validate_destination```で、```Destination```が ```settings.assertion_consumer_service_url```と合ってないぞ!と怒られている

https://github.com/onelogin/ruby-saml/blob/master/lib/onelogin/ruby-saml/response.rb#L603-L625

空の場合はSAML認証レスポンスの ```Destination``` に ```{recipient}```という値が入ってくる

設定した場合

空の場合


#### 対応方法

上記バリデーションをパスするには、下記のような設定をする必要がありました。

* ```Audience``` の設定値は、 ```saml_settings``` の ```settings.issuer``` と一致させる(今回は```http://localhost:3000/saml/metadata```)
* ```Recipient``` の設定値は、 SP の ACS URL と一致させる(今回は```http://localhost:3000/saml/acs```)

#### なぜこのバリデーションが必要なのか考察

<a href="https://support.onelogin.com/hc/en-us/articles/202673944-Use-the-OneLogin-SAML-Test-Connector" target="_blank" rel="nofollow noopener">OneLogin SAML Test Connectorのドキュメント</a>だと、```Audience``` と ```Recipient```は Not Requiredとなっています。なのにそれらを空欄にすることで、ruby-saml でバリデーションが失敗するのには疑問がありました。

何故なのかあれこれ理由を考えてみたのですが、ruby-samlの```is_valid?``` では、 **レスポンスの返答先の信頼性を必ず確認したい** からなのかな、と個人的には考えています。

そもそも OneLogin における ```Audience``` と ```Recipient``` は何なのでしょうか。<a href="https://support.onelogin.com/hc/en-us/articles/202673944-Use-the-OneLogin-SAML-Test-Connector" target="_blank" rel="nofollow noopener">OneLogin SAML Test Connectorのドキュメント</a>から引用して確認してみます。

> The Recipient will tell you exactly who the SAML response is for, but the Audience will tell you, at a broader level, where the response should go. So for example, the Recipient could be Yankee Stadium, while the Audience could be New York City.
>
> (意訳)Recipient は SAMLレスポンスの返答先を正確に示しますが、 Audience は広範なレベルで、レスポンスがどこに向かうべきかを示します。 例えば、Recipientはヤンキースタジアム、Audience はニューヨーク市と捉えることが出来ます。

つまり、```Audience``` と ```Recipient```はSAMLレスポンスの返答先を定めるような役割があるということがわかります。

こちらを踏まえ、```SamlController#acs```のアクションで ```response.is_valid?``` をつけない場合を考えてみます。この場合、OneLogin SAML Test Connector の Configuration で、```ACS URL```のみ指定してさえすれば、OneLoginのログイン成功後、 ```ACS URL```にリダイレクトされ、そのままログインが出来てしまいます。

しかしその場合、アプリのACS URLに飛んでくるOneLoginからのSAML認証レスポンスが、どこをレスポンス返答先として送られたものなのか、という検証がアプリ側でできなくなります。もしかしたら別のアプリに向けたレスポンスが飛んできたとしても、アプリ側でそれを検知する術がなくなってしまうということになります。

ですので、 ```Audience``` と ```Recipient``` を指定することで、正しいSPへのレスポンスであるかどうかも検証することで、セキュリティレベルを担保しているのかなと思いました。

ちなみに```Audience``` には SPエンティティIDとも呼ばれる、SPのmetadata を返すURLを指定するようです。

今回の場合```http://localhost:3000/saml/metadata``` ですが、こちらにアクセスすると下記のような XML ファイルが表示されます。

```entityID``` や、```Location``` などが明示されていることがわかります。




md:NameIDFormaturn:oasis:names:flag_tc:SAML:1.1:nameid-format:emailAddress



参考:https://github.com/onelogin/ruby-saml/issues/325

## 終わりに

長くなりましたが以上です。OneLogin の SAML 認証は奥が深いですね。。

## 参考

* <a href="https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language" target="_blank" rel="nofollow noopener">Security Assertion Markup Language - Wikipedia</a>
* <a href="https://en.wikipedia.org/wiki/Identity_provider" target="_blank" rel="nofollow noopener">Identity provider - Wikipedia</a>
* <a href="https://qiita.com/katsuhiko/items/1960f96661cdf6daf63b" target="_blank" rel="nofollow noopener">SSO を実現するための SAML2.0 の実装。まずはサンプルを動かす</a>

54
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?