きっかけ
とあるWebアプリケーション開発において、以下の2つを同時に実現する要件があった。 ①ログイン画面からIDとパスワードを入力することでForm認証ができる ②HTTPリクエストヘッダの特定の属性の値で認証ができる②は、既存の外部認証システムが、認証成功時にHTTPリクエストヘッダにトークンを設定してリダイレクトするため、それを利用したSSOを実現するという要件。
①単独、または②単独の実装サンプルはインターネットの随所に見られるんだけど、併用するパターンは見受けられなかった(かつそれなりにテクニカルだった)ので、どうやって実現したかを紹介する。
ソース
以下のように設定する。 https://github.com/ShandyGaffLover/sample001/blob/master/src/main/resources/spring-security.xml解説
「PRE_AUTH_FILTER」と「FORM_LOGIN_FILER」の2つをカスタマイズする。 <sec:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="formLoginFilter" />
PRE_AUTH_FILTERのカスタマイズ
「PRE_AUTH_FILTER」のカスタマイズは以下のようにRequestHeaderAuthenticationFilterクラスを利用する。Form認証と併用する都合上、exceptionIfHeaderMissingプロパティをfalseに、continueFilterChainOnUnsuccessfulAuthenticationプロパティをtrueに設定する。
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="sm_authenticationManager" />
<property name="exceptionIfHeaderMissing" value="false"/>
<property name="continueFilterChainOnUnsuccessfulAuthentication" value="true"/>
</bean>
ここで、AuthenticationManager(とAuthenticationProvider)は以下のように定義する。
<sec:authentication-manager id="sm_authenticationManager">
<sec:authentication-provider ref="preauthAuthProvider" />
</sec:authentication-manager>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="customUserDetailsService"/>
</bean>
</property>
</bean>
AuthenticationProviderにはPreAuthenticatedAuthenticationProviderクラスを利用する。このときpreAuthenticatedUserDetailsServiceプロパティに与えるUserDetailsServiceはUserDetailsByNameServiceWrapperクラスによりラッパーされたものでなければならない点に注意。
FORM_LOGIN_FILERのカスタマイズ
「FORM_LOGIN_FILER」のカスタマイズは以下の通りである。 <sec:authentication-manager id="form_authenticationManager">
<sec:authentication-provider user-service-ref="customUserDetailsService" >
</sec:authentication-provider>
</sec:authentication-manager>
<bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="form_authenticationManager" />
<property name="authenticationSuccessHandler" ref="formLoginSuccessHandler" />
<property name="authenticationFailureHandler" ref="formLoginFailureHandler" />
<property name="filterProcessesUrl" value="/login" />
</bean>
ここで、カスタムフィルターを使うので、以下のようにタグのentry-point-ref属性を設定する必要がある点に注意。
<sec:http auto-config="false" use-expressions="true" entry-point-ref="loginUrlAuthenticationEntryPoint">
~中略~
<sec:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="formLoginFilter" />
</sec:http>
<bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/loginPage" />
</bean>
実物
以下から動作確認ができる。[^1] https://shandygafflover.herokuapp.com/①ログイン画面から、usernameに「user」、passwordに「password」を入力しログインすると、認証される。
②HTTPリクエストヘッダの属性「SM_USER」に、値「user」を設定1すると、認証される。
ここでは以下の記事で紹介されているModHeaderを利用する。
ブラウザのHTTPヘッダーに外部から認証情報を設定する - Qiita
https://qiita.com/utwang/items/1eeb25d27e8acea33a8a
実行結果
HTTPリクエストヘッダの属性「SM_USER」に、値「user」を設定する。 ![requestheader.png](https://qiita-image-store.s3.amazonaws.com/0/322008/7b04e084-5929-255d-8d10-7ee600665ee6.png)HTTPリクエストヘッダに属性「SM_USER」が設定されていない場合はログイン画面に遷移するので、usernameとpasswordにより認証される。
参考URL
Spring Security 使い方メモ 認証・認可 - Qiita
https://qiita.com/opengl-8080/items/032ed0fa27a239bdc1cc
03-1.Spring Security 2の設定方法 - soracane
https://sites.google.com/site/soracane/home/springnitsuite/spring-security/spring-securityno-settei-houhou
テックノート – spring security 認証処理を自作する方法を解説
http://javatechnology.net/spring/spring-security-login-filter-original/
Spring Securityを適用するときの作業メモ - なみひらブログ
http://namihira.hatenablog.com/entry/20160817/1471396975
AbstractAuthenticationProcessingFilter (Spring Security 4.2.6.RELEASE API)
https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html
Spring Security Java - Multiple Authentication Manager - 2 bean found error - Stack Overflow
https://stackoverflow.com/questions/32105846/spring-security-java-multiple-authentication-manager-2-bean-found-error
-
リクエストヘッダの属性名「SM_USER」はあくまでも一例。実際の開発では、属性名は推測しにくいものとし機密事項とすべき。 ↩