spring securityでlogin画面を使わない様な独自の認証方式を実現する方法について記載するページ。
TL;DR
さくっと実装方法が知りたい場合はspring security preauth
でググると速いかも。
結論を先に書いておくとAbstractPreAuthenticatedProcessingFilterとAuthenticationUserDetailsServiceを実装すれば一通りの認証は実装できそうである。※ただし認証のみ。細かな部分については周辺の実装が必要になる。
spring securityのカスタム認証
oauthでもないけど認証情報は手元になくて、自アプリでは認証せず他の認証サーバと連携してといった独自の認証を実装しなければならないことがまれにある。そういった場合はSpring securityが持っているForm認証や、ベーシック認証で対応できないので独自の認証方式を実装しなきゃと思うことがありますが、そういった場合でもSpring securityで対応できます。
例えば、他のシステムと連携してユーザ認証後、認証結果のみが連携されるまたは、認証Tokenがリクエストヘッダーやクエリパラメータなどに付与されて送られてきてそれを認証サーバに問い合わせを行って認証するとか。他にも様々な独自の認証方式があるかと思います。そういった場合でもSpring Securityで対応できるようにSpring securityは設計されているため認証方式を1からスクラッチで作る必要はありません。Spring securityの用意しているinterfaceを実装することでそういった独自の認証も簡単に実装ができ、認証部分以降はSpring securityの仕組みが使用できます。例えばSessionからユーザを特定してというような仕組みを独自に作るのは結構辛いのでSpiring securityを使いたいですよね。ちなみにですが、Spring securityのお作法に従わず独自に認証してHttpSessionに状態を書き込んだり、Spring securityのContextを直接操作するようなことはしないほうがいいです。以下の様なコードが出てくる場合は何かがおかしいです。(自分の一番最初の独自認証はこんな感じで対応してしまったので・・・)
SecurityContextHolder.setContext(securityContext) |
Spring公式ドキュメントの
Pre-Authentication Scenariosを参照しています。詳細が知りたい場合はこのドキュメントを読んでください。
このページではざっくり公式ドキュメントを読んで概要を掴み、後半で簡単な実装をやってみます。
Spring securityのお作法
Spring Securityが用意しているinterfaceについて。
AbstractPreAuthenticatedProcessingFilter
HTTP Request毎にgetUserPrincipal()メソッドを呼び出すことによってユーザーを識別します。
認証成功・失敗時のHandlerはこのFilterで追加できます。
- setAuthenticationFailureHandler
- setAuthenticationSuccessHandler
HTTP Requestからユーザー情報を抽出するか、またはHTTP Requestに含まれるTokenなどを利用し認証サーバに問い合わせし、ユーザ情報(Principal / Credentials)を返すAbstractPreAuthenticatedProcessingFilterのメソッドを実装します。
このクラスは、SecurityContextの現在の内容をチェックし、空の場合は、HTTPリクエストからユーザー情報を抽出し、AuthenticationManagerに渡します。あくまでもこのFilterはPrincipalとCredentialsを抽出する実装で認証は後続で行う。
次のメソッドをオーバーライドします。
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); |
これらを呼びだした後、フィルタは返されたデータを含むPreAuthenticatedAuthenticationTokenを作成し、認証のためにSubmitします。
他のSpring Security認証フィルタと同様に、pre-authenticationフィルタは、セッション識別子と認証オブジェクトの詳細プロパティでIPアドレスを発信元などの追加情報を格納するためのオブジェクトWebAuthenticationDetailsを作成するauthenticationDetailsSourceを持っています。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
フィルタがこのクラスのインスタンスであるauthenticationDetailsSourceで構成されている場合は、権限情報は、「マッピング可能なROLE」が予めセットされており、それぞれについてのisUserInRole(String型のROLE)メソッドを呼び出すことによって得られます。クラスが設定されMappableAttributesRetrieverからこれらを取得します。実装では、アプリケーションのコンテキストでリストをハードコーディングし、web.xmlファイル内のロールの
Spring securityが提供しているauthenticationUserDetailsあたりを継承しておけば問題なさそうです。独自に実装する場合はRoleも自分でやってねってことでしょうか。
Roleを管理するためにGrantedAuthorityを実装したクラスを作りROLEの制御をします。
PreAuthenticatedAuthenticationProvider
PreAuthenticatedAuthenticationProviderは認証をAuthenticationUserDetailsServiceに委譲することによって認証を行います。AuthenticationUserDetailsServiceは標準のUserDetailsServiceに似ていますが、ユーザー名だけではなく認証オブジェクトも取ります。
public interface AuthenticationUserDetailsService { |
認証失敗の場合はUsernameNotFoundExceptionをthrowすればよさそうですね。
このインタフェースは、他の用途を有していてもよいが、事前認証では前節で説明したように、認証オブジェクトにパッケージ化されたauthoritiesへのアクセスを可能にします。 PreAuthenticatedGrantedAuthoritiesUserDetailsServiceクラスは、認証オブジェクトにパッケージ化されたauthoritiesへのアクセスを可能にします。あるいは、UserDetailsByNameServiceWrapperの実装を介して、標準UserDetailsServiceに委任することができます。
Http403ForbiddenEntryPoint
AuthenticationEntryPointは、技術的な概要の章で説明しました。(認証失敗のExeptionがthrowされたら403をレスポンスするぜみたいな話。)通常は、保護されたリソースにアクセスしようとすると認証されていないユーザーのための認証処理をする責任がありますが、pre-authenticationの場合には、これは適用されません。あなたが他の認証機構との組み合わせでpre-authenticationを使用していない場合にのみ、このクラスのインスタンスでExceptionTranslationFilterを構成します。ユーザーはnull認証の結果AbstractPreAuthenticatedProcessingFilterによって拒否された場合には呼び出されます。呼び出された場合、それは、常に403forbiddenのレスポンスコードを返します。
以下の様な実装。認証に失敗し403を返す時にリダイレクトしたりできる。public class HogeForbiddenEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendRedirect("/auth-error.html");
}
}
EntryEndpointを指定しないとform認証のときのように認証が失敗したときのリダイレクトが動作しないので注意が必要。
403だけでなく他のEndpointはInterface AuthenticationEntryPointの実装として幾つか用意されている。
Interface AuthenticationEntryPoint
LoginUrlAuthenticationEntryPointをendpointとして指定して、リダイレクト先のURIの設定はbuildRedirectUrlToLoginPageメソッドをOverrideすれば良い。
Endpointの指定はjavaConfigで以下のように指定する。
|
Endpointの指定にexceptionHandling()が必要なのはExceptionをHandlingしてリダイレクト先とかを決めているからと理解すれば自然な設計か。
Request-Header Authentication
外部認証システムは、HTTP要求に特定のヘッダを設定することにより、アプリケーションに情報を供給することができます。このよく知られた例はSM_USERというヘッダーにユーザ名を渡し方法です。これは単にヘッダからユーザー名を抽出し認証に利用するというメカニズムです。クラスRequestHeaderAuthenticationFilterによってサポートされています。デフォルトではヘッダ名のSM_USERを使用します。詳細については、Javadocを参照してください。
J2EE Container Authentication
クラスJ2eePreAuthenticatedProcessingFilterは、HttpServletRequestのUserPrincipalプロパティからユーザ名を抽出します。このユーザ名を利用して認証を行います。
独自認証の実装方式
例えば、認証サーバからリクエストヘッダーにTOKENがセットされてアプリ側にリダイレクトされて、そのTOKENを認証サーバに問い合わせることでユーザ認証するといった場合をやってみる。
MyPreAuthenticatedProcessingFilter
AbstractPreAuthenticatedProcessingFilter
をextendしたクラスで認証情報を取得する(Principal/Credentials)を独自認証用に実装する。
4j |
MyAuthenticationUserDetailsService
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>
をimplementしたクラスで認証を行う。
前のMyPreAuthenticatedProcessingFilterで設定したPrincipal/CredentialsがPreAuthenticatedAuthenticationTokenに詰められてMyAuthenticationUserDetailsServiceに渡されます。tokenからPrincipal/Credentialsを取得して認証を行います。ここではCredentialsからTOKENを取り出して認証サーバに問い合わせを行う処理を書きます。(実装は書きませんが。)
4j |
UserAuthority (AdminAuthority)
ただのRoleクラス。public class UserAuthority implements GrantedAuthority {
public String getAuthority() {
return "ROLE_USER";
}
}
SecurityConfig
“/“以外は認証が必要。あとは定義したClassを使用するように設定する。”/logout”でログアウトし、preAuthenticatedProcessingFilterをフィルターとして利用するように設定する。preAuthenticatedProcessingFilterで認証が行われると以後は認証は行われずLogin状態となる。
|
まとめ
Spring securityで独自の認証に対応出きることが分かった。
おわり。
参考
- http://docs.spring.io/spring-security/site/docs/4.2.x-SNAPSHOT/reference/html/preauth.html
- http://stackoverflow.com/questions/9902783/preauthentication-with-spring-security-based-on-url-parameters
- http://qiita.com/masato_ka/items/519144098ba5370f1a26
- http://qiita.com/masatsugumatsus/items/7111983f04c08a5df1f8
- http://qiita.com/kazuki43zoo/items/e925f134e65d7595aa3c