はじめに
昨年に引き続き今年もNRI OpenStandiaのメンバを中心にAdvent Calendarを書きます。昨年はKeycloakを中心とした構成としましたが、今年度はKeycloakに限定せず、オープンソースという枠で書いていきます(1日目はKeycloakが登場しますが。。)。それでは宜しくお願いします。
今回実現したかったこと
皆さん、最近アプリケーションは何で作っていますか?認証はどう作っていますか?昨今いろんな選択肢があると思います。今回私が関わったプロジェクト要件は下記のようなものでした(最近のエンタープライズ案件ではよくありそうな構成かと思います)。
- Spring Framework 5によるアプリケーション開発が想定されている
- 既存の認証サーバに認証情報、属性情報が保管されている
- 既存の認証サーバはSAMLに対応しておりアプリケーションへの認証連携はSAMLを利用できる
ということで、必然的に、Spring Security SAMLが候補に挙がりまして検証をしました。
この過程でわかったことなどを書いていきたいと思います。
そもそもSpringにおけるSecurityプロジェクトの構成
現在、Springは22のメインプロジェクト、2つのコミュニティプロジェクト、3つのAtticプロジェクトにて運営されています。今回、取り扱うSpring Security SAMLはメインプロジェクトの1つであるSpring Securityの中のサブプロジェクトとして開発が行われています。
- https://spring.io/projects
- https://spring.io/projects/spring-security
- http://projects.spring.io/spring-security-saml/
Spring Security SAML の 動作確認
ということで、Spring Security SAMLの導入に入ろうと思います。執筆現在は、1.0.3がGAとしてリリースされています。
GitHubやMavenリポジトリ、Springサイトのポストでは1.0.4のリリースがアナウンスされていますが、Spring Security SAML公式サイトの表記はまだ1.0.3がGAのようなので今回は1.0.3にて検証を進めます。
上記にて公開されているdependenciesを追記しビルドすればライブラリのインストールが可能ですが、今回は公式ガイドに載っているサンプルアプリケーションを動作させ動作検証をします。
こちらがSpring Security SAMLの公式ガイドです。
全体を一読したほうがよいのは間違いないのですが、まずサンプルアプリケーションを動作させるのに最低限必要となる箇所は4. Quick start guideです。以下に、私が検証したときの手順を記載します。環境に合わせて適宜読み替えてください。また、公式ガイドで紹介されているIdPであるidp.ssocircle.comは無料枠でログインできる回数が制限されているようです。ということで、Keycloakとかすぐに建てられるIdPを準備して検証することをお勧めします(下記もKeycloakでの検証手順になっています)。
それでは、4. Quick start guideに沿って実施をしていきます。
4. Quick start guide
4.1. Pre-requisites
前提となるJava、Maven環境は下記のとおりです。
Java 1.6+ SDK
Apache Maven
今回は以下のバージョンにて検証をしています。
$ java -version
java version "1.8.0_181"
$ mvn -version
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T03:33:14+09:00)
4.2. Installation steps
4.2.1. Downloading sample application
まずはサンプルアプリケーションを含む1.0.3 GA版バイナリを下記よりダウンロードし展開します。
4.2.2. Configuration of IDP metadata
サンプルアプリケーションではSP起動時にIdPからメタデータを動的に取得してくる作りになっています。今回はIdPにKeycloakを使いますので自分が使うIdPが提供するメタデータ取得URLを指定します。今回はlocalhostに、openstandiaというレルム名で8180ポートを使用してKeycloakを立てるため下記のように指定します。
<bean class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider">
<!-- URL containing the metadata -->
<constructor-arg>
<value type="java.lang.String">http://localhost:8180/auth/realms/openstandia/protocol/saml/descriptor</value>
</constructor-arg>
<!-- Timeout for metadata loading in ms -->
<constructor-arg>
<value type="int">15000</value>
</constructor-arg>
<property name="parserPool" ref="parserPool"/>
</bean>
4.2.3. Generation of SP metadata
自分が使用するentityIdを指定するため以下のように修正します。
<bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<property name="entityId" value="urn:test:hiroshiaida:yokohama"/>
<property name="extendedMetadata">
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
<property name="idpDiscoveryEnabled" value="true"/>
</bean>
</property>
</bean>
4.2.4. Compilation
mvnコマンドでパッケージングをしますが、その前にpom.xml
を一部修正します。Spring Security SAML 1.0.3に含まれるpom.xml
にも関わらず、記述が1.0.0になっている箇所があるので修正をします。
$ diff -u ../../spring-security-saml-1.0.3.RELEASE/sample/pom.xml pom.xml
--- ../../spring-security-saml-1.0.3.RELEASE/sample/pom.xml 2017-12-20 17:34:40.000000000 +0900
+++ pom.xml 2018-12-01 03:30:02.783266900 +0900
@@ -5,7 +5,7 @@
<groupId>org.springframework.security.extensions</groupId>
<artifactId>spring-security-saml2-sample</artifactId>
<packaging>war</packaging>
- <version>1.0.0.RELEASE-SNAPSHOT</version>
+ <version>1.0.3.RELEASE-SNAPSHOT</version>
<name>Spring Security SAML v2 sample webapp</name>
<description>Spring Security SAML v2 sample webapp</description>
<url>https://github.com/SpringSource/spring-security-saml</url>
@@ -119,7 +119,7 @@
<dependency>
<groupId>org.springframework.security.extensions</groupId>
<artifactId>spring-security-saml2-core</artifactId>
- <version>1.0.0.RELEASE</version>
+ <version>1.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
上記修正を実施した後、以下のmvnコマンドでパッケージングをします。
$ mvn package
4.2.5. Deployment
以下のコマンドでTomcat上でサンプルアプリケーションを起動します。
$ mvn tomcat7:run
起動には成功しますが、恐らく下記のようなExceptionが発生するはずです。
- Error retrieving metadata from http://localhost:8180/auth/realms/openstandia/protocol/saml/descriptor
java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
Keycloakがまだ起動していないためこのエラーは想定通りなので次に進みます。
4.2.6. Uploading of SP metadata to the IDP
Download current SP metadata:
次にSPメタデータをIdP(Keycloak)にアップロードします。
Spring Security SAMLのSPメタデータは下記のURLに直接アクセスすることで取得可能です。
spring_saml_metadata.xml`がダウンロードできれば取得成功です。
Upload SP metadata to the IDP:
上記で取得したメタデータをKeycloakにアップロードします。
Keycloakの場合、以下の手順でSAML設定を簡単に追加できます(Keycloakは日本語化されている前提で記載しています)。
- 管理者でログイン
- 設定の「クライアント」をクリックし、画面右端にある「作成」をクリック
- 「インポート」の横にある「ファイルを選択」し先ほどのspring_saml_metadata.xmlを指定してアップロード
- 「保存」をクリック
SAML接続に関する基本的な設定は上記でできますが、ユーザ属性のマッピングなど連携要件に従ってKeycloakの設定を変更していく必要があります。
今回の検証にて使用したKeycloak設定、ポイントとなる設定は本記事文末にキャプチャにて示します。適宜ご参考にしてください。
4.3. Testing single sign-on and single logout
IdP側の設定が完了していますので、SP側を再起動しシングルサインオンとシングルログアウトの動作を確認します。下記のように画面遷移できていれば正常に動作しています。
http://localhost:8080/spring-security-saml2-sample/ にアクセス。Spring Security SAMLのサンプルアプリケーショントップ画面が表示されます。
http://localhost:8180/auth/realms/openstandia のチェックボックスを選択後、「Start single sign-on」を押下。Keycloakのログイン画面が表示されます。
ID/パスワードを入力後、ログインボタンを押下。認証後、自動的にSpring Security SAMLのサンプルアプリケーションログイン後画面が表示されます。
「Metadata Administration」をクリック。Spring Security SAMLのサンプルアプリケーションAdministrator用ログイン画面が表示されます。
ID/パスワードを入力後、ログインボタンを押下。認証後、Spring Security SAMLのサンプルアプリケーションAdministrator用ログイン後画面が表示されます。
Spring Security SAML の Spring 5系対応状況
ここまででSpring Security SAMLのサンプルアプリケーションの動作確認ができました。最初の「実現したかったこと」でさらっと記載したのですが、今回は、Spring Security SAMLをSpring Framework 5で動かしたい、というのがもともとの要件でした。実は上の手順で記載した方法ではSpring Security SAMLはSpring Framework 3ベースで動いています。こちらはmvnコマンドであったり、パッケージングしたwarを見て頂くとわかります。
$ mvn dependency:tree
(中略)
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ spring-security-saml2-sample ---
[INFO] org.springframework.security.extensions:spring-security-saml2-sample:war:1.0.3.RELEASE-SNAPSHOT
[INFO] +- javax.servlet:jstl:jar:1.2:compile
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.6.3:compile
[INFO] | +- org.slf4j:slf4j-api:jar:1.6.3:compile
[INFO] | \- log4j:log4j:jar:1.2.16:compile
[INFO] +- org.springframework.security.extensions:spring-security-saml2-core:jar:1.0.3.RELEASE:compile
[INFO] | +- org.opensaml:opensaml:jar:2.6.1:compile
[INFO] | | +- org.opensaml:openws:jar:1.5.1:compile
[INFO] | | | +- org.opensaml:xmltooling:jar:1.4.1:compile
[INFO] | | | | +- org.bouncycastle:bcprov-jdk15:jar:1.46:compile
[INFO] | | | | \- ca.juliusdavies:not-yet-commons-ssl:jar:0.3.9:compile
[INFO] | | | +- commons-httpclient:commons-httpclient:jar:3.1:compile
[INFO] | | | \- org.apache.santuario:xmlsec:jar:1.5.6:compile
[INFO] | | +- commons-codec:commons-codec:jar:1.7:compile
[INFO] | | +- commons-collections:commons-collections:jar:3.2.1:compile
[INFO] | | +- commons-lang:commons-lang:jar:2.6:compile
[INFO] | | +- org.apache.velocity:velocity:jar:1.7:compile
[INFO] | | +- org.owasp.esapi:esapi:jar:2.0.1:compile
[INFO] | | +- joda-time:joda-time:jar:2.2:compile
[INFO] | | +- xerces:xercesImpl:jar:2.10.0:runtime
[INFO] | | +- xalan:serializer:jar:2.7.1:runtime
[INFO] | | +- xml-resolver:xml-resolver:jar:1.2:runtime
[INFO] | | \- xalan:xalan:jar:2.7.1:runtime
[INFO] | +- org.springframework.security:spring-security-core:jar:3.1.2.RELEASE:compile
[INFO] | +- org.springframework.security:spring-security-web:jar:3.1.2.RELEASE:compile
[INFO] | \- xml-apis:xml-apis:jar:1.4.01:runtime
[INFO] +- org.springframework.security:spring-security-config:jar:3.1.2.RELEASE:compile
[INFO] | \- aopalliance:aopalliance:jar:1.0:compile
[INFO] +- org.springframework:spring-core:jar:3.1.2.RELEASE:compile
[INFO] | +- org.springframework:spring-asm:jar:3.1.2.RELEASE:compile
[INFO] | \- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- org.springframework:spring-beans:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:3.1.2.RELEASE:compile
[INFO] | \- org.springframework:spring-expression:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-aop:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-web:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-webmvc:jar:3.1.2.RELEASE:compile
[INFO] | \- org.springframework:spring-context-support:jar:3.1.2.RELEASE:compile
[INFO] +- javax.servlet:jsp-api:jar:2.0:provided
[INFO] | \- javax.servlet:servlet-api:jar:2.4:provided
[INFO] \- junit:junit:jar:4.4:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Spring Security SAMLは、リリースノートおよびリファレンスガイド上、Spring Framework 5に対応していそうなのですが、
On behalf of the community, I’m pleased to announce the release of Spring Security SAML 1.0.3.RELEASE which makes some minor changes to work with Spring Framework 5.0.0+ while keeping backward compatibility.
The current version of SAML Extension has been tested to work with Spring 3.1.2, Spring Security 3.1.2 and OpenSAML 2.6.1. Later versions of these libraries are likely to be compatible without need for modifications.
「likely to be compatible」など不安になる記載もあるため、今回は以下のアプローチでSpring Security SAMLのサンプルアプリケーションをSpring Framework 5で動かしてみました。
Spring Framework関係のjarを全て5系に変更
今回はSpring Security SAMLサンプルアプリケーションのpom.xmlに指定してある依存jarのバージョンをSpring Framework 5のバージョンに書き換えました。
- Spring IO PlatformのEOL発表に伴い、spring-boot-starter-parent、spring-boot-dependenciesの使用が推奨されています(Spring IO Platform end-of-life announcement)が、今回は動作しない場合jar依存性など1つ1つ指定して試行錯誤したかったので上記のようなアプローチにしました。
- 元々組み込もうとしていたSpring Frameworkバージョンとの関係から、今回はSpring IO PlatformのCairo SR1に合わせたバージョン指定にしています。Cairo SR1では、Spring Security関連のバージョンは5.0.5、Spring Framework関連のバージョンは5.0.6になっています。
- spring-security-core、spring-security-webは指定しないと3系が入ってきてしまうため、明示的に5.0.5を指定します。
pom.xml
の修正箇所は下記の通りです。
$ diff -u ../../spring-security-saml-1.0.3.RELEASE_Keycloak/sample/pom.xml pom.xml
--- ../../spring-security-saml-1.0.3.RELEASE_Keycloak/sample/pom.xml 2018-12-01 03:30:02.783266900 +0900
+++ pom.xml 2018-12-01 04:34:36.179213100 +0900
@@ -113,7 +113,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
- <version>1.6.3</version>
+ <version>1.7.25</version>
<scope>compile</scope>
</dependency>
<dependency>
@@ -125,43 +125,55 @@
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.5.RELEASE</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-core</artifactId>
+ <version>5.0.5.RELEASE</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-web</artifactId>
+ <version>5.0.5.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
- <version>3.1.2.RELEASE</version>
+ <version>5.0.6.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
@@ -173,7 +185,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.4</version>
+ <version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
上記でサンプルアプリケーションの動作確認
上記環境で動作確認をします。最初はいろいろ動作しなかったのですが、結論として、サンプルアプリケーションの設定やコードを修正するのみで動作しました。下記の変更だけで動作したため、ライブラリ競合は特になく、Spring Security SAML 1.0.3のサンプルアプリケーションが、ただ単にSpring Framework 5に対応していなかっただけと判断しています。
CSRF対策 その1
SAMLのPOSTバインディング方式ではSPに対してSAML ResponseをPOSTしますが、SAMLのエンドポイントに対しても上記CSRF対策は有効になるため、サンプルアプリケーションをそのまま動作させるとリクエストは403エラーとなってしまいます(CSRF対策が有効になっている証拠ですが)。
今回は下記のURLを参考にSAMLエンドポイントと通常エンドポイントを分けて対応をしました。
The other, easier is to define Spring SAML endpoints in a separate http configuration which will not have the csrf protection enabled.
<!-- Secured pages with SAML as entry point -->
- <security:http entry-point-ref="samlEntryPoint" use-expressions="false">
+ <security:http pattern="/saml/**" entry-point-ref="samlEntryPoint" use-expressions="false">
+ <security:csrf disabled="true"/>
+ <security:intercept-url pattern="/saml/**" access="IS_AUTHENTICATED_FULLY"/>
+ <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
+ <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
+ </security:http>
+
+ <!-- Secured pages with Not SAML as entry point -->
+ <security:http pattern="/**" entry-point-ref="samlEntryPoint" use-expressions="false">
+ <security:csrf disabled="false"/>
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
CSRF対策 その2
Spring Securityは3→4に上がるタイミングでCSRF対策がデフォルトで有効になるように変更されています。
As Spring Security 4.0+ CSRF Protection is now enabled by default.
そのためCSRF対策をしていないアプリケーションは必然的に対応をすることが要求されます。Spring Security SAML 1.0.3のサンプルアプリケーションではAdministrator用の画面へのログインにPOSTを使っているため下記のようにCSRFトークンを埋め込む対策が必要です。
+ <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</div>
</div>
form loginのデフォルト値変更
Spring Securityは3→4に上がるタイミングでフォームログイン時のパラメータ名が変更になっています。
If the is being used within an application, then some of the default attributes have changed.
サンプルアプリケーションにて、j_username、j_passwordを使っているのでこちらも変更が必要です。
<table>
<tr>
<td><label for="username">User:</label></td>
- <td><input type='text' name='j_username' id="username" class="text" value='admin'></td>
+ <td><input type='text' name='username' id="username" class="text" value='admin'></td>
</tr>
<tr>
<td><label for="password">Password:</label></td>
- <td><input type='password' name='j_password' id="password" class="text" value="admin"/></td>
+ <td><input type='password' name='password' id="password" class="text" value="admin"/></td>
</tr>
<tr>
<td></td>
<td><input name="submit" class="button" type="submit" value="Login"/></td>
</tr>
</table>
パスワードエンコーダーの対応
Spring Security 4→5における変更でパスワードエンコーダーの取り扱いも変更になっています。
サンプルアプリケーションではパスワードエンコーディングは行わず生パスワードがコンフィギュレーションファイルに記載されておりますのでBCryptPasswordEncoderを使うように変更します。
+ <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
+
<security:authentication-manager alias="authenticationManager">
<!-- Register authentication manager for SAML provider -->
<security:authentication-provider ref="samlAuthenticationProvider"/>
<!-- Register authentication manager for administration UI -->
<security:authentication-provider>
+ <security:password-encoder ref="passwordEncoder"/>
<security:user-service id="adminInterfaceService">
- <security:user name="admin" password="admin" authorities="ROLE_ADMIN"/>
+ <security:user name="admin" password="$2a$10$ozKJfV0iGFKuK4J8Rm.3nuCtpe9dxK0fb/05WfoDejxQXxUcAjNzm" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
デフォルトの属性名変更
こちらはSpring MVCのアップグレードによる影響です。各種画面にてcommandNameが利用されている箇所がありますが、全てmodelAttributeに置き換える必要があります。
<div class="post-body">
<p><a href="<c:url value="/saml/web/metadata"/>"><< Back</a></p>
- <form:form commandName="metadata" action="create">
+ <form:form modelAttribute="metadata" action="create">
<table>
<tr>
<td><label for="store">Store for the current session:</label></td>
metadataView.jsp、providerView.jspも同様に修正が必要です。
アプリケーションへの組み込みにあたって
上記によりサンプルアプリケーションの動作確認が完了しました。この後、実際のアプリケーションへの組み込みにあたっては下記のような対応が必要となります。
- 既存のアプリケーションビルド環境への組み込み
- 既存のアプリケーションのweb.xmlへの組み込み
- 既存のアプリケーションへの属性情報の引き渡し
実はここからは既存のアプリケーションの構造やフレームワーク、標準化の指針などにより様々な設計パターンが考えられます。ガイドではSAMLUserDetailsServiceを拡張する方法が提示されていますし、プロジェクトの標準化の指針に従った部品作成をするという方法もあるかと思います。
最後に
サンプルアプリケーションは未整備ですが、Spring Security SAML 1.0.3がSpring Framework 5で動作することが確認できました。SAMLは古い古いと言われながらもまだ利用されることの多いプロトコルですし、Springは今後5系が中心となるのは必至かと思います。本記事が今後利用される方の参考になれば幸いです。
現在、Spring Security SAMLの開発は下記にて行われています。
-
https://github.com/spring-projects/spring-security-saml
現在はSpring Security SAMLの2系の開発が進められています。開発リソースの問題か、2018年9月にMilestone Releaseの2.0.0.M17が出てからあまり動きが無さそうに見えますが、GA版のリリースを待ちたいと思います。