9.2. 認証¶
目次
- Overview
- How to use
- How to extend
- Appendix
9.2.1. Overview¶
本節では、Spring Securityが提供している認証機能について説明する。
認証処理は、アプリケーションを利用するユーザーの正当性を確認するための処理である。
ユーザーの正当性を確認するためのもっとも標準的な方法は、アプリケーションを使用できるユーザーをデータストアに登録しておき、 利用者が入力した認証情報(ユーザー名とパスワードなど)と照合する方法である。 ユーザーの情報を登録しておくデータストアにはリレーショナルデータベースを利用するのが一般的だが、ディレクトリサービスや外部システムなどを利用するケースもある。
また、利用者に認証情報を入力してもらう方式もいくつか存在する。 HTMLの入力フォームを使う方式やRFCで定められているHTTP標準の認証方式(Basic認証やDigest認証など)を利用するのが一般的だが、 OpenID認証やシングルサインオン認証などの認証方式を利用するケースもある。
本節では、HTMLの入力フォームで入力した認証情報とリレーショナルデータベースに格納されているユーザー情報を照合して認証処理を行う実装例を紹介しながら、 Spring Securityの認証機能の使い方を説明する。
9.2.1.1. 認証処理のアーキテクチャ¶
Spring Securityは、以下のような流れで認証処理を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントは、認証処理を行うパスに対して資格情報(ユーザー名とパスワード)を指定してリクエストを送信する。
|
(2)
|
Authentication Filterは、リクエストから資格情報を取得して、
AuthenticationManager クラスの認証処理を呼び出す。 |
(3)
|
ProviderManager (デフォルトで使用されるAuthenticationManager の実装クラス)は、実際の認証処理をAuthenticationProvider インタフェースの実装クラスに委譲する。 |
9.2.1.1.1. Authentication Filter¶
Authentication Filterは、認証方式に対する実装を提供するサーブレットフィルタである。 Spring Securityがサポートしている主な認証方式は以下の通り。
クラス名 | 説明 |
---|---|
UsernamePasswordAuthenticationFilter |
フォーム認証用のサーブレットフィルタクラスで、HTTPリクエストのパラメータから資格情報を取得する。
|
BasicAuthenticationFilter |
Basic認証用のサーブレットフィルタクラスで、HTTPリクエストの認証ヘッダから資格情報を取得する。
|
DigestAuthenticationFilter |
Digest認証用のサーブレットフィルタクラスで、HTTPリクエストの認証ヘッダから資格情報を取得する。
|
RememberMeAuthenticationFilter |
Remember Me認証用のサーブレットフィルタクラスで、HTTPリクエストのCookieから資格情報を取得する。
Remember Me認証を有効にすると、ブラウザを閉じたりセッションタイムアウトが発生しても、ログイン状態を保つことができる。
|
これらのサーブレットフィルタは、 フレームワーク処理で紹介したAuthentication Filterの1つである。
Note
Spring Securityによってサポートされていない認証方式を実現する必要がある場合は、
認証方式を実現するためのAuthentication Filter
を作成し、Spring Securityに組み込むことで実現することが可能である。
9.2.1.1.2. AuthenticationManager¶
AuthenticationManager
は、認証処理を実行するためのインタフェースである。
Spring Securityが提供するデフォルト実装(ProviderManager
)では、
実際の認証処理はAuthenticationProvider
に委譲し、AuthenticationProvider
で行われた認証処理の処理結果をハンドリングする仕組みになっている。
9.2.1.1.3. AuthenticationProvider¶
AuthenticationProvider
は、認証処理の実装を提供するためのインタフェースである。
Spring Securityが提供している主なAuthenticationProvider
の実装クラスは以下の通り。
クラス名 | 説明 |
---|---|
DaoAuthenticationProvider |
データストアに登録しているユーザーの資格情報とユーザーの状態をチェックして認証処理を行う実装クラス。
チェックで必要となる資格情報とユーザーの状態は
UserDetails というインタフェースを実装しているクラスから取得する。 |
Note
Spring Securityが提供していない認証処理を実現する必要がある場合は、
認証処理を実現するためのAuthenticationProvider
を作成し、Spring Securityに組み込むことで実現することが可能である。
9.2.2. How to use¶
認証機能を使用するために必要となるbean定義例や実装方法について説明する。
本項では Overviewで説明したとおり、 HTMLの入力フォームで入力した認証情報とリレーショナルデータベースに格納されているユーザー情報を照合して認証処理を行う方法について説明する。
9.2.2.1. フォーム認証¶
Spring Securityは、以下のような流れでフォーム認証を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントは、フォーム認証を行うパスに対して資格情報(ユーザー名とパスワード)をリクエストパラメータとして送信する。
|
(2)
|
UsernamePasswordAuthenticationFilter クラスは、リクエストパラメータから資格情報を取得して、AuthenticationManager の認証処理を呼び出す。 |
(3)
|
UsernamePasswordAuthenticationFilter クラスは、AuthenticationManager から返却された認証結果をハンドリングする。認証処理が成功した場合は
AuthenticationSuccessHandler のメソッドを呼び出し、認証処理が失敗した場合はAuthenticationFailureHandler のメソッドを呼び出し、画面遷移を行う。 |
9.2.2.1.1. フォーム認証の適用¶
フォーム認証を使用する場合は、以下のようなbean定義を行う。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
<sec:form-login> タグを定義することで、フォーム認証が有効になる。 |
Tip
auto-config属性について
<sec:http>
には、フォーム認証(<sec:form-login>
タグ)、Basic認証(<sec:http-basic>
タグ)、ログアウト(<sec:logout>
タグ)に対するコンフィギュレーションを自動で行うか否かを指定するauto-config
属性が用意されている。
デフォルト値はfalse
(自動でコンフィギュレーションしない)となっており、Spring Securityのリファレンスドキュメントでもデフォルト値の使用が推奨されている。
本ガイドラインでも、明示的にタグを指定するスタイルを推奨する。
要素名 説明 <form-login>
フォーム認証処理を行うSecurity Filter(UsernamePasswordAuthenticationFilter
)が適用される。<http-basic>
RFC1945に準拠したBasic認証を行うSecurity Filter(BasicAuthenticationFilter
)が適用される。詳細な利用方法は、BasicAuthenticationFilterのJavaDocを参照されたい。<logout>
ログアウト処理を行うSecurity Filter(LogoutFilter
)が適用される。ログアウト処理の詳細については、「ログアウト」を参照されたい。
なお、 auto-config
を定義しない場合は、フォーム認証(<sec:form-login>
タグ)、もしくはBasic認証(<sec:http-basic>
タグ)を定義する必要がある。
これは、ひとつのSecurityFilterChain
(<sec:http>
)内には、ひとつ以上のAuthentication FilterのBean定義が必要であるという、Spring Securityの仕様をみたすためである。
9.2.2.1.2. デフォルトの動作¶
Spring Securityのデフォルトの動作では、/login
に対してGETメソッドでアクセスするとSpring Securityが用意しているデフォルトのログインフォームが表示され、
ログインボタンを押下すると/login
に対してPOSTメソッドでアクセスして認証処理を行う。
9.2.2.1.3. ログインフォームの作成¶
Spring Securityはフォーム認証用のログインフォームをデフォルトで提供しているが、そのまま利用するケースは少ない。 ここでは、自身で作成したログインフォームをSpring Securityに適用する方法を説明する。
まず、ログインフォームを表示するためのJSPを作成する。 ここでは、Spring MVCでリクエストをうけてログインフォームを表示する際の実装例になっている。
- ログインフォームを表示するためのJSPの作成例(xxx-web/src/main/webapp/WEB-INF/views/login/loginForm.jsp)
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%-- omitted --%>
<div id="wrapper">
<h3>Login Screen</h3>
<%-- (1) --%>
<c:if test="${param.containsKey('error')}">
<t:messagesPanel messagesType="error"
messagesAttributeName="SPRING_SECURITY_LAST_EXCEPTION"/> <%-- (2) --%>
</c:if>
<form:form action="${pageContext.request.contextPath}/login" method="post"> <%-- (3) --%>
<table>
<tr>
<td><label for="username">User Name</label></td>
<td><input type="text" id="username" name="username"></td>
</tr>
<tr>
<td><label for="password">Password</label></td>
<td><input type="password" id="password" name="password"></td>
</tr>
<tr>
<td> </td>
<td><button>Login</button></td>
</tr>
</table>
</form:form>
</div>
<%-- omitted --%>
項番 | 説明 |
---|---|
(1)
|
認証エラーを表示するためのエリア。
|
(2)
|
認証エラー時に出力させる例外メッセージを出力する。
共通ライブラリで提供している
<t:messagesPanel> タグを使用して出力することを推奨する。<t:messagesPanel> タグの使用方法については、「メッセージ管理」を参照されたい。なお、認証エラーが発生した場合、Spring Securityのデフォルトの設定で使用される、
org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler では、認証エラー時に発生した例外オブジェクトをSPRING_SECURITY_LAST_EXCEPTION という属性名で、リダイレクト時はセッション、フォワード時はリクエストスコープに格納する。 |
(3)
|
ユーザー名とパスワードを入力するためのログインフォーム。
ここではユーザー名を
username 、パスワードをpassowrd というリクエストパラメータで送信する。また、
<form:form> を使用することで、CSRF対策用のトークン値がリクエストパラメータで送信される。CSRF対策については、「CSRF対策」で説明する。
|
つぎに、作成したログインフォームをSpring Securityに適用する。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login
login-page="/login/loginForm"
login-processing-url="/login" /> <!-- (1)(2) -->
<sec:intercept-url pattern="/login/**" access="permitAll"/> <!-- (3) -->
<sec:intercept-url pattern="/**" access="isAuthenticated()"/> <!-- (4) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
login-page 属性にログインフォームを表示するためのパスを指定する。匿名ユーザーが認証を必要とするWebリソースにアクセスした場合は、この属性に指定したパスにリダイレクトしてログインフォームを表示する。
ここでは、Spring MVCでリクエストを受けてログインフォームを表示している。
詳細は 「Spring MVCでリクエストを受けてログインフォームを表示する」を参照されたい。
|
(2)
|
login-processing-url 属性に認証処理を行うためのパスを指定する。デフォルトのパスも
/login であるが、ここでは明示的に指定することとする。 |
(3)
|
ログインフォームが格納されている
/login パス配下に対し、すべてのユーザーがアクセスできる権限を付与する。Webリソースに対してアクセスポリシーの指定方法については、「認可」を参照されたい。
|
(4)
|
アプリケーションで扱うWebリソースに対してアクセス権を付与する。
上記例では、Webアプリケーションのルートパスの配下に対して、認証済みユーザーのみがアクセスできる権限を付与している。
Webリソースに対してアクセスポリシーの指定方法については、「認可」を参照されたい。
|
Note
Spring Security 4.0における変更
Spring Security 4.0から、以下の設定のデフォルト値が変更されている
- username-parameter
- password-parameter
- login-processing-url
- authentication-failure-url
9.2.2.2. 認証成功時のレスポンス¶
Spring Securityは、認証成功時のレスポンスを制御するためのコンポーネントとして、
AuthenticationSuccessHandler
というインタフェースと実装クラスを提供している。
実装クラス | 説明 |
---|---|
SavedRequestAwareAuthenticationSuccessHandler |
認証前にアクセスを試みたURLにリダイレクトを行う実装クラス。
デフォルトで使用される実装クラス。
|
SimpleUrlAuthenticationSuccessHandler |
defaultTargetUrl にリダイレクト又はフォワードを行う実装クラス。 |
9.2.2.2.1. デフォルトの動作¶
Spring Securityのデフォルトの動作では、認証前にアクセスを拒否したリクエストをHTTPセッションに保存しておいて、
認証が成功した際にアクセスを拒否したリクエストを復元してリダイレクトする。
認証したユーザーにリダイレクト先へのアクセス権があればページが表示され、アクセス権がなければ認可エラーとなる。
この動作を実現するために使用されるのが、SavedRequestAwareAuthenticationSuccessHandler
クラスである。
ログインフォームを明示的に表示してから認証処理を行った後の遷移先は、Spring Securityのデフォルトの設定では
Webアプリケーションのルートパス(“/
” )となっているため、認証成功時はWebアプリケーションのルートパスにリダイレクトされる。
9.2.2.3. 認証失敗時のレスポンス¶
Spring Securityは、認証失敗時のレスポンスを制御するためのコンポーネントとして、
AuthenticationFailureHandler
というインタフェースと実装クラスを提供している。
実装クラス | 説明 |
---|---|
SimpleUrlAuthenticationFailureHandler |
指定したパス(
defaultFailureUrl )にリダイレクト又はフォワードを行う実装クラス。 |
ExceptionMappingAuthenticationFailureHandler |
認証例外と遷移先のURLをマッピングすることができる実装クラス。
Spring Securityはエラー原因毎に発生する例外クラスが異なるため、この実装クラスを使用するとエラーの種類毎に遷移先を切り替えることが可能である。
|
DelegatingAuthenticationFailureHandler |
認証例外と
AuthenticationFailureHandler をマッピングすることができる実装クラス。ExceptionMappingAuthenticationFailureHandler と似ているが、認証例外毎にAuthenticationFailureHandler を指定できるので、より柔軟な振る舞いをサポートすることができる。 |
9.2.2.3.1. デフォルトの動作¶
Spring Securityのデフォルトの動作では、ログインフォームを表示するためのパスにerror
というクエリパラメータが付与されたURLにリダイレクトする。
例として、ログインフォームを表示するためのパスが/login
の場合は/login?error
にリダイレクトされる。
9.2.2.4. DB認証¶
Spring Securityは、以下のような流れでDB認証を行う。
項番 | 説明 |
---|---|
(1)
|
Spring Securityはクライアントからの認証依頼を受け、
DaoAuthenticationProvider の認証処理を呼び出す。 |
(2)
|
DaoAuthenticationProvider は、UserDetailsService のユーザー情報取得処理を呼び出す。 |
(3)
|
UserDetailsService の実装クラスは、データストアからユーザー情報を取得する。 |
(4)
|
UserDetailsService の実装クラスは、データストアから取得したユーザー情報からUserDetails を生成する。 |
(5)
|
DaoAuthenticationProvider は、UserDetailsService から返却されたUserDetails とクライアントが指定した認証情報との照合を行い、クライアントが指定したユーザーの正当性をチェックする。 |
Note
Spring Securityが提供するDB認証
Spring Securityは、ユーザー情報をリレーショナルデータベースからJDBC経由で取得するための実装クラスを提供している。
org.springframework.security.core.userdetails.User
(UserDetails
の実装クラス)org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
(UserDetailsService
の実装クラス)
これらの実装クラスは最低限の認証処理(パスワードの照合、有効ユーザーの判定)しか行わないため、そのまま利用できるケースは少ない。
そのため、本ガイドラインでは、UserDetails
とUserDetailsService
の実装クラスを作成する方法について説明する。
9.2.2.4.1. UserDetailsの作成¶
UserDetails
は、認証処理で必要となる資格情報(ユーザー名とパスワード)とユーザーの状態を提供するためのインタフェースで、以下のメソッドが定義されている。
AuthenticationProvider
としてDaoAuthenticationProvider
を使用する場合は、アプリケーションの要件に合わせてUserDetails
の実装クラスを作成する。
UserDetailsインタフェース
public interface UserDetails extends Serializable {
String getUsername(); // (1)
String getPassword(); // (2)
boolean isEnabled(); // (3)
boolean isAccountNonLocked(); // (4)
boolean isAccountNonExpired(); // (5)
boolean isCredentialsNonExpired(); // (6)
Collection<? extends GrantedAuthority> getAuthorities(); // (7)
}
項番 | メソッド名 | 説明 |
---|---|---|
(1)
|
getUsername |
ユーザー名を返却する。
|
(2)
|
getPassword |
登録されているパスワードを返却する。
このメソッドで返却したパスワードとクライアントから指定されたパスワードが一致しない場合は、
DaoAuthenticationProvider はBadCredentialsException を発生させる。 |
(3)
|
isEnabled |
有効なユーザーかを判定する。有効な場合は
true を返却する。無効なユーザーの場合は、
DaoAuthenticationProvider はDisabledException を発生させる。 |
(4)
|
isAccountNonLocked |
アカウントのロック状態を判定する。ロックされていない場合は
true を返却する。アカウントがロックされている場合は、
DaoAuthenticationProvider はLockedException を発生させる。 |
(5)
|
isAccountNonExpired |
アカウントの有効期限の状態を判定する。有効期限内の場合は
true を返却する。有効期限切れの場合は、
DaoAuthenticationProvider はAccountExpiredException を発生させる。 |
(6)
|
isCredentialsNonExpired |
資格情報の有効期限の状態を判定する。有効期限内の場合は
true を返却する。有効期限切れの場合は、
DaoAuthenticationProvider はCredentialsExpiredException を発生させる。 |
(7)
|
getAuthorities |
ユーザーに与えられている権限リストを返却する。
このメソッドは認可処理で使用される。
|
Note
認証例外による遷移先の切り替え
DaoAuthenticationProvider
が発生させる例外毎に画面遷移を切り替えたい場合は、
AuthenticationFailureHandler
としてExceptionMappingAuthenticationFailureHandler
を使用すると実現することができる。
例として、ユーザーのパスワードの有効期限が切れた際にパスワード変更画面に遷移させたい場合は、
ExceptionMappingAuthenticationFailureHandler
を使ってCredentialsExpiredException
をハンドリングすると画面遷移を切り替えることができる。
詳細は、認証失敗時のレスポンスのカスタマイズを参照されたい。
Note
Spring Securityが提供する資格情報
Spring Securityは、資格情報(ユーザー名とパスワード)とユーザーの状態を保持するための実装クラス(org.springframework.security.core.userdetails.User
)を提供しているが、
このクラスは認証処理に必要な情報しか保持することができない。
一般的なアプリケーションでは、認証処理で使用しないユーザーの情報(ユーザーの氏名など)も必要になるケースが多いため、User
クラスをそのまま利用できるケースは少ない。
ここでは、アカウントの情報を保持するUserDetails
の実装クラスを作成する。
本例はUser
を継承することでも実現することができるが、UserDetails
を実装する方法の例として紹介している。
- UserDetailsの実装クラスの作成例
public class AccountUserDetails implements UserDetails { // (1)
private final Account account;
private final Collection<GrantedAuthority> authorities;
public AccountUserDetails(
Account account, Collection<GrantedAuthority> authorities) {
// (2)
this.account = account;
this.authorities = authorities;
}
// (3)
public String getPassword() {
return account.getPassword();
}
public String getUsername() {
return account.getUsername();
}
public boolean isEnabled() {
return account.isEnabled();
}
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
// (4)
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
// (5)
public Account getAccount() {
return account;
}
}
項番 | 説明 |
---|---|
(1)
|
UserDetails インタフェースを実装したクラスを作成する。 |
(2)
|
ユーザー情報と権限情報をプロパティに保持する。
|
(3)
|
UserDetails インタフェースに定義されているメソッドを実装する。 |
(4)
|
本節の例では、「アカウントのロック」「アカウントの有効期限切れ」「資格情報の有効期限切れ」に対するチェックは未実装であるが、要件に合わせて実装されたい。
|
(5)
|
認証処理成功後の処理でアカウント情報にアクセスできるようにするために、getterメソッドを用意する。
|
Spring Securityは、UserDetails
の実装クラスとしてUser
クラスを提供している。
User
クラスを継承すると資格情報とユーザーの状態を簡単に保持することができる。
- Userクラスを継承したUserDetails実装クラスの作成例
public class AccountUserDetails extends User {
private final Account account;
public AccountUserDetails(Account account, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<GrantedAuthority> authorities) {
super(account.getUsername(), account.getPassword(),
account.isEnabled(), true, true, true, authorities);
this.account = account;
}
public Account getAccount() {
return account;
}
}
9.2.2.4.2. UserDetailsServiceの作成¶
UserDetailsService
は、認証処理で必要となる資格情報とユーザーの状態をデータストア
から取得するためのインタフェースで、以下のメソッドが定義されている。
AuthenticationProvider
としてDaoAuthenticationProvider
を使用する場合は、
アプリケーションの要件に合わせてUserDetailsService
の実装クラスを作成する。
- UserDetailsServiceインタフェース
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
ここでは、データベースからアカウント情報を検索して、UserDetails
のインスタンス
を生成するためのサービスクラスを作成する。
本サンプルでは、SharedService
を使用して、アカウント情報を取得している。
SharedService
については、Serviceの実装を参照されたい。
- AccountSharedServiceインタフェースの作成例
public interface AccountSharedService {
Account findOne(String username);
}
- AccountSharedServiceの実装クラスの作成例
// (1)
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
@Inject
AccountRepository accountRepository;
// (2)
@Override
public Account findOne(String username) {
Account account = accountRepository.findOneByUsername(username);
if (account == null) {
throw new ResourceNotFoundException("The given account is not found! username="
+ username);
}
return account;
}
}
項番 | 説明 |
---|---|
(1)
|
AccountSharedService インタフェースを実装したクラスを作成し、@Service を付与する。上記例では、コンポーネントスキャン機能を使って
AccountSharedServiceImpl をDIコンテナに登録している。 |
(2)
|
データベースからアカウント情報を検索する。
アカウント情報が見つからない場合は、共通ライブラリの例外である
ResourceNotFoundException を発生させる。Repositoryの作成例については、「Spring Securityチュートリアル」を参照されたい。
|
- UserDetailsServiceの実装クラスの作成例
// (1)
@Service
@Transactional
public class AccountUserDetailsService implements UserDetailsService {
@Inject
AccountSharedService accountSharedService;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
try {
Account account = accountSharedService.findOne(username);
// (2)
return new AccountUserDetails(account, getAuthorities(account));
} catch (ResourceNotFoundException e) {
// (3)
throw new UsernameNotFoundException("user not found", e);
}
}
// (4)
private Collection<GrantedAuthority> getAuthorities(Account account) {
if (account.isAdmin()) {
return AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN");
} else {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
}
}
項番 | 説明 |
---|---|
(1)
|
UserDetailsService インタフェースを実装したクラスを作成し、@Service を付与する。上記例では、コンポーネントスキャン機能を使って
UserDetailsService をDIコンテナに登録している。 |
(2)
|
AccountSharedService を使用してアカウント情報を取得する。アカウント情報が見つかった場合は、
UserDetails を生成する。上記例では、ユーザー名、パスワード、ユーザーの有効状態をアカウント情報から取得している。
|
(3)
|
アカウント情報が見つからない場合は、
UsernameNotFoundException を発生させる。 |
(4)
|
ユーザーが保持する権限(ロール)情報を生成する。ここで生成した権限(ロール)情報は、認可処理で使用される。
|
Note
認可で使用する権限情報
Spring Securityの認可処理は、ROLE_
で始まる権限情報をロールとして扱う。
そのため、ロールを使用してリソースへのアクセス制御を行う場合は、 ロールとして扱う権限情報にROLE_
プレフィックスを付与する必要がある。
Note
認証例外情報の隠蔽
Spring Securityのデフォルトの動作では、UsernameNotFoundException
はBadCredentialsException
という例外に変換してからエラー処理を行う。
BadCredentialsException
は、クライアントから指定された資格情報のいずれかの項目に誤りがあることを通知するための例外であり、具体的なエラー理由がクライアントに通知されることはない。
9.2.2.4.3. DB認証の適用¶
作成したUserDetailsService
を使用して認証処理を行うためには、
DaoAuthenticationProvider
を有効化して、作成したUserDetailsService
を適用する必要がある。
- spring-security.xmlの定義例
<sec:authentication-manager> <!-- (1) -->
<sec:authentication-provider user-service-ref="accountUserDetailsService" /> <!-- (2) -->
</sec:authentication-manager>
項番 | 説明 |
---|---|
(1)
|
AuthenticationManager をbean定義する。 |
(2)
|
<sec:authentication-manager> 要素内に <sec:authentication-provider> 要素を定義する。本定義により、デフォルト設定の
DaoAuthenticationProvider が有効になる。 |
Note
Spring Security 5から、passwordEncoder
という名前のBeanを定義していると、sec:authentication-provider
配下にsec:password-encoder
要素を指定しない場合に自動的に参照されるようになった。
これにより、TERASOLUNA Server Framework for Java 5.5.1.RELEASEからは基本的にsec:password-encoder
の指定を省略することができる。
Spring Security 4ではsec:password-encoder
要素を省略した場合、PasswordEncoder
としてPlaintextPasswordEncoder
が使用されていた。Spring Security 5ではsec:password-encoder
要素を省略し、かつpasswordEncoder
という名前のBeanが存在しない場合、org.springframework.security.crypto.factory.PasswordEncoderFactories
を利用して生成したDelegatingPasswordEncoder
が使用される。
9.2.2.5. パスワードのハッシュ化¶
パスワードをデータベースなどに保存する場合は、パスワードそのものではなくパスワードの ハッシュ値を保存するのが一般的である。
Spring Securityは、パスワードをハッシュ化するためのインタフェースと実装クラスを 提供しており、認証機能と連携して動作する。
Spring Securityは以下のインタフェースを提供している。
org.springframework.security.crypto.password.PasswordEncoder
Note
Spring Security 3.1.4以降、非推奨のインタフェースであったorg.springframework.security.authentication.encoding.PasswordEncoder
は、
Spring Security 5.0で廃止された。
org.springframework.security.crypto.password.PasswordEncoderのメソッド定義
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}
メソッド名 | 説明 |
---|---|
encode |
パスワードをハッシュ化するためのメソッド。
アカウントの登録処理やパスワード変更処理などでデータストアに保存するパスワードをハッシュ化する際に使用できる。
|
matches |
平文のパスワードとハッシュ化されたパスワードを照合するためのメソッド。
このメソッドはSpring Securityの認証処理でも利用されるが、パスワード変更処理などで現在のパスワードや過去に使用していたパスワードと照合する際にも使用できる。
|
Spring Securityは、PasswordEncoder
インタフェースの実装クラスとして以下の3つのいずれかを使用することを推奨している。
また、本ガイドラインではこれらのクラスを直接使用せず、後述するDelegatingPassowordEncoder
を通して使用することを推奨する。
実装クラス | 説明 |
---|---|
Pbkdf2PasswordEncoder |
PBKDF2アルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
本ガイドラインでは、このクラスを使用することを推奨している。
詳細は、Pbkdf2PasswordEncoderのJavaDocを参照されたい。
|
BCryptPasswordEncoder |
BCryptアルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
詳細は、BCryptPasswordEncoderのJavaDocを参照されたい。
|
SCryptPasswordEncoder |
SCryptアルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
詳細は、SCryptPasswordEncoderのJavaDocを参照されたい。
|
Note
OWASP(Open Web Application Security Project)ではFIPSに準ずるPBKDF2アルゴリズムが推奨されている。
TERASOLUNA Server Framework for Javaでは5.5.1.RELEASEから、OWASP(Open Web Application Security Project)で推奨されるPBKDF2アルゴリズムの使用を推奨する。
これに伴い、ブランクプロジェクトが提供するPasswordEncoder
の定義も、BCryptPasswordEncoder
からデフォルトでPbkdf2PasswordEncoder
を使用する定義に変更している。
Note
SCryptPasswordEncoder
を使用する場合は、ブランクプロジェクトのデフォルト設定から変更する必要がある。
applicationContext.xml
のコメントアウトを外し、SCryptPasswordEncoder
の定義を有効化する。
applicationContext.xml
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <constructor-arg name="idForEncode" value="pbkdf2" /> <constructor-arg name="idToPasswordEncoder"> <map> <!-- ommited --> <!-- When using SCryptPasswordEncoder, you need to add bcprov-jdk15on.jar to the dependency. <entry key="scrypt"> <bean class="org.springframework.security.crypto.scrypt.SCryptPasswordEncoder" /> </entry> --> </map> </constructor-arg> </bean>
依存ライブラリとして不足しているbcprov-jdk15on
を追加する。
pom.xmlに以下のdependencyを追加すれば良い。
pom.xml
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>${bouncycastle.version}</version> <!-- (1) --> </dependency>
項番 説明 (1) 任意のバージョンを指定する。
Note
アプリケーションの要件によっては、上記以外の非推奨なPasswordEncoder
の実装クラスを利用する必要がある場合もある。
「非推奨アルゴリズムのPasswordEncoderの利用」では非推奨の実装クラスの一つであるMessageDigestPasswordEncoder
を利用する方法について解説する
9.2.2.5.1. DelegatingPasswordEncoder¶
DelegatingPasswordEncoder
は、ハッシュ化されたパスワードの照合に複数のPasswordEncoder
から適切なものを選択するためのストラテジインタフェースである。
これにより、データベース等に格納された様々なアルゴリズムでハッシュ化されたパスワードを、アプリケーションの変更無しに扱うことが可能となる。
なお、DelegatingPasswordEncoder
がハッシュ化されたアルゴリズムを判定するには、ハッシュ化されたパスワードの先頭にアルゴリズムを示すキーを含む必要があり、DelegatingPasswordEncoder
がパスワードをハッシュ化する際には、このキーが自動的に付与される。
項番 | 説明 |
---|---|
(1)
|
User1、User2についてパスワードの照合を行う。データストアには
DelegatingPasswordEncoder でハッシュ化したパスワードが格納されている。データストアに格納されたパスワードは
DelegatingPasswordEncoder がハッシュ化を行っており、User1はBCryptPasswordEncoder 、User2はPbkdf2PasswordEncoder が用いられている。なお、この解説ではデータストアから
DaoAuthenticationProvider にユーザ情報を引き渡す際に経由するUserDetailsService の実装クラス等を省略しているため注意されたい。 |
(2)
|
DelegatingPasswordEncoder を用いて照合を行う。照合の際は、データストアに格納されたハッシュ化されたパスワードからプレフィックスを読み取り適切なPasswordEncoder に処理を委譲する。User1のハッシュ値はプレフィックスとして
bcrypt が付与されているためBCryptPasswordEncoder で照合が行われ、User2のハッシュ値はpbkdf2 が付与されているためPbksdf2PasswordEncoder で照合が行われる。 |
ブランクプロジェクトではPbkdf2PasswordEncoder
を使用するDelegatingPasswordEncoder
が定義されている。
ここではブランクプロジェクトで定義されているPasswordEncoder
をもとに解説を行う。
- applicationContext.xmlの定義
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <!-- (1) -->
<constructor-arg name="idForEncode" value="pbkdf2" /> <!-- (2) -->
<constructor-arg name="idToPasswordEncoder"> <!-- (3) -->
<map> <!-- (4) -->
<entry key="pbkdf2">
<bean class="org.springframework.security.crypto.password.Pbkdf2PasswordEncoder" />
</entry>
<entry key="bcrypt">
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
</entry>
<entry key="scrypt">
<bean class="org.springframework.security.crypto.scrypt.SCryptPasswordEncoder" />
</entry>
</map>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
DelegatingPasswordEncoder をid passwordEncoder で定義する。 |
(2)
|
idToPasswordEncoder で登録したPasswordEncoder の内、ハッシュ化に使用するもののkey 値をidForEncode に指定する。 |
(3)
|
idToPasswordEncoder にPasswordEncoder の実装を格納したMap を指定する。 |
(4)
|
PasswordEncoder の実装をMap に格納する。ハッシュ化したパスワードのプレフィックスが
Map のkey と一致すると、そのkey で格納されたPasswordEncoder を使用して照合が行われる。また、前述の
idForEncode とkey が一致するPasswordEncoder を使用してハッシュ化が行われる。ハッシュ化の際には
key に指定した値がプレフィックスとして付与される。 |
Warning
セキュリティに関わる注意点
実際のアプリケーション開発では、セキュリティ上のリスクを軽減するためidForEncode
とidToPasswordEncoder
のkey
値にアルゴリズム名を推測できないような値を指定することを推奨する。
また、PasswordEncoder
インタフェースの実装クラスを利用する際は PasswordEncoderのカスタマイズ についても参照し、セキュリティ要件を満たすように変更を加えられたい。
ハッシュ化を行うクラスでは、PasswordEncoder
をDIコンテナからインジェクションして使用する。
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
@Inject
AccountRepository accountRepository;
@Inject
PasswordEncoder passwordEncoder; // (1)
public Account register(Account account, String rawPassword) {
// omitted
String encodedPassword = passwordEncoder.encode(rawPassword); // (2)
account.setPassword(encodedPassword);
// omitted
return accountRepository.save(account);
}
}
項番 | 説明 |
---|---|
(1)
|
PasswordEncoder をインジェクションする。 |
(2)
|
インジェクションした
PasswordEncoder のメソッドを呼び出す。ここでは、データストアに保存するパスワードをハッシュ化している。
|
Note
Pbkdf2以外のアルゴリズムを使用する場合
パスワードのハッシュ化に使用するPasswordEncoder
を変更するには、idForEncode
に使用したいPasswordEncoder
のkey
値(bcrypt
やscrypt
)に指定すれば良い。
Warning
既存のアプリケーションにDelegatingPasswordEncoderを適用する際の注意点
PasswordEncoderに関する注意点
既に運用しているシステムでは、パスワードにプレフィックスは付与されていない。 プレフィックスが付与されていないパスワードをそのまま照合に使用するには、以下で示すように
defaultPasswordEncoderForMatches
プロパティで照合に使用するエンコーダを指定する必要がある。
- applicationContext.xmlの変更
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <constructor-arg name="idForEncode" value="pbkdf2" /> <constructor-arg name="idToPasswordEncoder"> <map> <entry key="pbkdf2"> <bean class="org.springframework.security.crypto.password.Pbkdf2PasswordEncoder" /> </entry> <!-- omitted --> </map> </constructor-arg> <property name="defaultPasswordEncoderForMatches" ref="passwordEncoderUsedBefore" /> <!-- (1) --> </bean>
項番 説明 (1)defaultPasswordEncoderForMatches
に移行前に使用していたPasswordEncoder
を指定する。これにより、プレフィックスが付与されていないハッシュ値に対して、指定したPasswordEncoder
で照合が行われるようになる。
defaultPasswordEncoderForMatches
プロパティに照合に使用するエンコーダを指定せずに、DelegatingPasswordEncoder
を使用してプレフィックスが付与されていないハッシュ値を照合しようとすると、デフォルトで設定されているUnmappedIdPasswordEncoder
が使用され、IllegalArgumentException
が発生する。
データストアに関する注意点
ハッシュ化されたパスワードを格納するデータベースなどでは、プレフィックスが付与されることを考慮する必要がある。
9.2.2.6. 認証イベントのハンドリング¶
Spring Securityは、Spring Frameworkが提供しているイベント通知の仕組みを利用して、 認証処理の処理結果を他のコンポーネントと連携する仕組みを提供している。
この仕組みを利用すると、以下のようなセキュリティ要件をSpring Securityの認証機能に組み込むことが可能である。
- 認証成功、失敗などの認証履歴をデータベースやログに保存する。
- パスワードを連続して間違った場合にアカウントをロックする。
認証イベントの通知は、以下のような仕組みで行われる。
項番 | 説明 |
---|---|
(1)
|
Spring Securityの認証機能は、認証結果(認証情報や認証例外)を
AuthenticationEventPublisher に渡して認証イベントの通知依頼を行う。 |
(2)
|
AuthenticationEventPublisher インタフェースのデフォルトの実装クラスは認証結果に対応する認証イベントクラスのインスタンスを生成し、
ApplicationEventPublisher に渡してイベントの通知依頼を行う。 |
(3)
|
ApplicationEventPublisher インタフェースの実装クラスは、ApplicationListener インタフェースの実装クラスにイベントを通知する。 |
(4)
|
ApplicationListener の実装クラスの一つであるApplicationListenerMethodAdaptor は、@org.springframework.context.event.EventListener が付与されているメソッドを呼び出してイベントを通知する。 |
Note
メモ
Spring 4.1まではApplicationListener
インタフェースの実装クラスを作成してイベントを受け取る必要があったが、
Spring 4.2からはPOJOに@EventListener
を付与したメソッドを実装するだけでイベントを受け取ることが可能である。
なお、Spring 4.2以降でも、従来通りApplicationListener
インタフェースの実装クラスを作成してイベントを受け取ることも可能である。
Spring Security使用しているイベントは、認証が成功したことを通知するイベントと認証が失敗したことを通知するイベントの2種類に分類される。 以下にSpring Securityが用意しているイベントクラスを説明する。
9.2.2.6.1. 認証成功イベント¶
認証が成功した時にSpring Securityが通知する主なイベントは以下の3つである。 この3つのイベントは途中でエラーが発生しなければ、以下の順番ですべて通知される。
イベントクラス | 説明 |
---|---|
AuthenticationSuccessEvent |
AuthenticationProvider による認証処理が成功したことを通知するためのイベントクラス。
このイベントをハンドリングすると、クライアントが正しい認証情報を指定したことを検知することが可能である。
なお、このイベントをハンドリングした後の後続処理でエラーが発生する可能性がある点に注意されたい。 |
SessionFixationProtectionEvent |
セッション固定攻撃対策の処理(セッションIDの変更処理)が成功したことを通知するためのイベントクラス。 このイベントをハンドリングすると、変更後のセッションIDを検知することが可能になる。 |
InteractiveAuthenticationSuccessEvent |
認証処理がすべて成功したことを通知するためのイベントクラス。 このイベントをハンドリングすると、画面遷移を除くすべての認証処理が成功したことを検知することが可能になる。 |
9.2.2.6.2. 認証失敗イベント¶
認証が失敗した時にSpring Securityが通知する主なイベントは以下の通り。 認証に失敗した場合は、いずれか一つのイベントが通知される。
イベントクラス | 説明 |
---|---|
AuthenticationFailureBadCredentialsEvent |
BadCredentialsException が発生したことを通知するためのイベントクラス。 |
AuthenticationFailureDisabledEvent |
DisabledException が発生したことを通知するためのイベントクラス。 |
AuthenticationFailureLockedEvent |
LockedException が発生したことを通知するためのイベントクラス。 |
AuthenticationFailureExpiredEvent |
AccountExpiredException が発生したことを通知するためのイベントクラス。 |
AuthenticationFailureCredentialsExpiredEvent |
CredentialsExpiredException が発生したことを通知するためのイベントクラス。 |
AuthenticationFailureServiceExceptionEvent |
AuthenticationServiceException が発生したことを通知するためのイベントクラス。 |
9.2.2.6.3. イベントリスナの作成¶
認証イベントの通知を受け取って処理を行いたい場合は、@EventListener
を付与したメソッドを実装したクラスを作成し、DIコンテナに登録する。
- イベントリスナクラスの実装例
@Component
public class AuthenticationEventListeners {
private static final Logger logger =
LoggerFactory.getLogger(AuthenticationEventListeners.class);
@EventListener // (1)
public void handleBadCredentials(
AuthenticationFailureBadCredentialsEvent event) { // (2)
log.info("Bad credentials is detected. username : {}", event.getAuthentication().getName());
// omitted
}
項番 | 説明 |
---|---|
(1)
|
@EventListener をメソッドに付与したメソッドを作成する。 |
(2)
|
メソッドの引数にハンドリングしたい認証イベントクラスを指定する。
|
上記例では、クライアントが指定した認証情報に誤りがあった場合に通知されるAuthenticationFailureBadCredentialsEvent
をハンドリングするクラスを作成する例としているが、
他のイベントも同じ要領でハンドリングすることが可能である。
Tip
総当たり攻撃による不正ログインの兆候を検出するための方法として、ログイン認証時のログを監視することがあげられる。
実装例のようなAuthenticationFailureBadCredentialsEvent
をハンドリングするイベントリスナを作成して
認証情報の誤りをログ情報として出力することで、Spring Securityを使用した認証時のログを監視することが可能になる。
9.2.2.7. ログアウト¶
Spring Securityは、以下のような流れでログアウト処理を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントは、ログアウト処理を行うためのパスにリクエストを送信する。
|
(2)
|
LogoutFilter は、LogoutHandler のメソッドを呼び出し、実際のログアウト処理を行う。 |
(3)
|
LogoutFilter は、LogoutSuccessHandler のメソッドを呼び出し、画面遷移を行う。 |
LogoutHandler
の実装クラスは複数存在し、それぞれ以下の役割をもっている。
実装クラス | 説明 |
---|---|
SecurityContextLogoutHandler |
ログインユーザーの認証情報のクリアとセッションの破棄を行うクラス。
|
CookieClearingLogoutHandler |
指定したクッキーを削除するためのレスポンスを行うクラス。
|
CsrfLogoutHandler |
CSRF対策用トークンの破棄を行うクラス。
|
これらのLogoutHandler
は、Spring Securityが提供しているbean定義をサポートするクラスが自動でLogoutFilter
に設定する仕組みになっているため、
基本的にはアプリケーションの開発者が直接意識する必要はない。
また、Remember Me認証機能 を有効にすると、Remember Me認証用のTokenを破棄するためのLogoutHandler
の実装クラスも設定される。
9.2.2.7.1. ログアウト処理の適用¶
ログアウト処理を適用するためには、以下のようなbean定義を行う。
- spring-security.xmlの定義例
<sec:http>
<!-- omitted -->
<sec:logout /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
<sec:logout> タグを定義することで、ログアウト処理が有効となる。 |
Note
Spring Security 4.0における変更
Spring Security 4.0から、以下の設定のデフォルト値が変更されている
- logout-url
Tip
Cookieの削除
本ガイドラインでは説明を割愛するが、 <sec:logout>
タグには、ログアウト時に指定したCookieを削除するためのdelete-cookies
属性が存在する。
ただし、この属性を使用しても正常にCookieが削除できないケースが報告されている。
詳細はSpring Securityの以下のJIRAを参照されたい。
9.2.2.7.2. デフォルトの動作¶
Spring Securityのデフォルトの動作では、/logout
というパスにリクエストを送るとログアウト処理が行われる。
ログアウト処理では、「ログインユーザーの認証情報のクリア」「セッションの破棄」が行われる。
また、
- CSRF対策を行っている場合は、「CSRF対策用トークンの破棄」
- Remember Me認証機能を使用している場合は、「Remember Me認証用のTokenの破棄」
も行われる
- ログアウト処理を呼び出すためのJSPの実装例
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%-- omitted --%>
<form:form action="${pageContext.request.contextPath}/logout" method="post"> <%-- (1) --%>
<button>ログアウト</button>
</form:form>
項番 | 説明 |
---|---|
(1)
|
Note
CSRFトークンの送信
CSRF対策を有効にしている場合は、CSRF対策用のトークンをPOSTメソッドで送信する必要がある。
9.2.2.8. ログアウト成功時のレスポンス¶
Spring Securityは、ログアウト成功時のレスポンスを制御するためのコンポーネントとして、
LogoutSuccessHandler
というインタフェースと実装クラスを提供している。
実装クラス | 説明 |
---|---|
SimpleUrlLogoutSuccessHandler |
指定したパス(
defaultTargetUrl )にリダイレクトを行う実装クラス。 |
9.2.2.8.1. デフォルトの動作¶
Spring Securityのデフォルトの動作では、ログインフォームを表示するためのパスにlogout
というクエリパラメータが付与されたURLにリダイレクトする。
例として、ログインフォームを表示するためのパスが/login
の場合は/login?logout
にリダイレクトされる。
9.2.2.9. 認証情報へのアクセス¶
認証されたユーザーの認証情報は、Spring Securityのデフォルト実装ではセッションに格納される。
セッションに格納された認証情報は、リクエスト毎にSecurityContextPersistenceFilter
クラスによってSecurityContextHolder
というクラスに格納され、同一スレッド内であればどこからでもアクセスすることができるようになる。
ここでは、認証情報からUserDetails
を取得し、取得したUserDetails
が保持している情報にアクセスする方法を説明する。
9.2.2.9.1. Javaからのアクセス¶
一般的な業務アプリケーションでは、「いつ」「誰が」「どのデータに」「どのようなアクセスをしたか」を記録する監査ログを取得することがある。 このような要件を実現する際の「誰が」は、認証情報から取得することができる。
- Javaから認証情報へアクセスする実装例
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication(); // (1)
String userUuid = null;
if (authentication.getPrincipal() instanceof AccountUserDetails) {
AccountUserDetails userDetails =
AccountUserDetails.class.cast(authentication.getPrincipal()); // (2)
userUuid = userDetails.getAccount().getUserUuid(); // (3)
}
if (log.isInfoEnabled()) {
log.info("type:Audit\tuserUuid:{}\tresource:{}\tmethod:{}",
userUuid, httpRequest.getRequestURI(), httpRequest.getMethod());
}
項番 | 説明 |
---|---|
(1)
|
SecurityContextHolder から認証情報(Authentication オブジェクト) を取得する。 |
(2)
|
Authentication#getPrincipal() メソッドを呼び出して、UserDetails オブジェクトを取得する。認証済みでない場合(匿名ユーザーの場合)は、匿名ユーザーであることを示す文字列が返却されるため注意されたい。
|
(3)
|
UserDetails から処理に必要な情報を取得する。ここでは、ユーザーを一意に識別するための値(UUID)を取得している。
|
Warning
認証情報へのアクセスと結合度
Spring Securityのデフォルト実装では、認証情報をスレッドローカルの変数に格納しているため、リクエストを受けたスレッドと同じスレッドであればどこからでもアクセス可能である。
この仕組みは便利ではあるが、認証情報を必要とするクラスがSecurityContextHolder
クラスに直接依存してしまうため、乱用するとコンポーネントの疎結合性が低下するので注意が必要である。
Spring Securityでは、Spring MVCの機能と連携してコンポーネント間の疎結合性を保つための仕組みを別途提供している。 Spring MVCとの連携方法については、「認証処理とSpring MVCの連携」で説明する。 本ガイドラインではSpring MVCとの連携を使用して認証情報を取得することを推奨する。
Note
認証処理用のフィルタ(FORM_LOGIN_FILTER)をカスタマイズする場合は、
<sec:concurrency-control>
要素の指定に加えて、以下の2つのSessionAuthenticationStrategy
クラスを有効化する必要がある。
org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
認証成功後にログインユーザ毎のセッション数をチェックするクラス。org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy
認証に成功したセッションをセッション管理領域に登録するクラス。
version 1.0.x.RELEASEで依存しているSpring Security 3.1では、org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy
というクラスが提供されていたが、
Spring Security 3.2より非推奨のAPIになり、Spring Security 4.0より廃止になっている。
Spring Security 3.1からSpring Security 3.2以降にバージョンアップする場合は、以下のクラスを組み合わせて使用するように変更する必要がある。
ConcurrentSessionControlAuthenticationStrategy
(Spring Security 3.2で追加)RegisterSessionAuthenticationStrategy
(Spring Security 3.2で追加)org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
具体的な定義方法については、 Spring Security Reference -Web Application Security (Concurrency Control)- のサンプルコードを参考にされたい。
9.2.2.9.2. JSPからのアクセス¶
一般的なWebアプリケーションでは、ログインユーザーのユーザー情報などを画面に表示することがある。 このような要件を実現する際のログインユーザーのユーザー情報は、認証情報から取得することができる。
- JSPから認証情報へアクセスする実装例
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%-- omitted --%>
ようこそ、
<sec:authentication property="principal.account.lastName"/> <%-- (1) --%>
さん。
項番 | 説明 |
---|---|
(1)
|
Spring Securityから提供されている
<sec:authentication> タグを使用して、認証情報(Authentication オブジェクト) を取得する。property 属性にアクセスしたいプロパティへのパスを指定する。ネストしているオブジェクトへアクセスしたい場合は、プロパティ名を”
. ” でつなげればよい。 |
Tip
認証情報の表示方法
ここでは、認証情報が保持するユーザー情報を表示する際の実装例を説明したが、var
属性とscope
属性を組み合わせて任意のスコープ変数に値を格納することも可能である。
ログインユーザーの状態によって表示内容を切り替えたい場合は、ユーザー情報を変数に格納しておき、JSTLのタグライブラリなどを使って表示を切り替えることが可能である。
上記の例は、以下のように記述することでも実現することができる。
本例では、scope
属性を省略しているため、page
スコープが適用される。
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%-- omitted --%> <sec:authentication var="principal" property="principal"/> <%-- omitted --%> ようこそ、 ${f:h(principal.account.lastName)} さん。
9.2.2.10. 認証処理とSpring MVCの連携¶
Spring Securityは、Spring MVCと連携するためのコンポーネントをいくつか提供している。 ここでは、認証処理と連携するためのコンポーネントの使い方を説明する。
9.2.2.10.1. 認証情報へのアクセス¶
Spring Securityは、認証情報(UserDetails
)をSpring MVCのコントローラーのメソッドに引き渡すためのコンポーネントとして、AuthenticationPrincipalArgumentResolver
クラスを提供している。
AuthenticationPrincipalArgumentResolver
を使用すると、コントローラーのメソッド引数としてUserDetails
インタフェースまたはその実装クラスのインスタンスを受け取ることができるため、コンポーネントの疎結合性を高めることができる。
認証情報(UserDetails
)をコントローラーの引数として受け取るためには、まずAuthenticationPrincipalArgumentResolver
をSpring MVCに適用する必要がある。
AuthenticationPrincipalArgumentResolver
を適用するためのbean定義は以下の通りである。
なお、ブランクプロジェクトにはAuthenticationPrincipalArgumentResolver
が設定済みである。
- spring-mvc.xmlの定義例
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- omitted -->
<!-- (1) -->
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
<!-- omitted -->
</mvc:argument-resolvers>
</mvc:annotation-driven>
項番 | 説明 |
---|---|
(1)
|
HandlerMethodArgumentResolver の実装クラスとして、AuthenticationPrincipalArgumentResolver をSpring MVCに適用する。 |
認証情報(UserDetails
)をコントローラーのメソッドで受け取る際は、以下のようなメソッドを作成する。
- 認証情報(UserDetails)を受け取るメソッドの作成例
@RequestMapping("account")
@Controller
public class AccountController {
public String view(
@AuthenticationPrincipal AccountUserDetails userDetails, // (1)
Model model) {
model.addAttribute(userDetails.getAccount());
return "profile";
}
}
項番 | 説明 |
---|---|
(1)
|
認証情報(
UserDetails ) を受け取るための引数を宣言し、@org.springframework.security.core.annotation.AuthenticationPrincipal を引数アノテーションとして指定する。AuthenticationPrincipalArgumentResolver は、@AuthenticationPrincipal が付与されている引数に認証情報(UserDetails )が設定される。 |
9.2.3. How to extend¶
本節では、Spring Securityが用意しているカスタマイズポイントや拡張方法について説明する。
Spring Securityは、多くのカスタマイズポイントを提供しているため、すべてのカスタマイズポイントを紹介することはできないため、ここでは代表的なカスタマイズポイントに絞って説明を行う。
9.2.3.1. フォーム認証のカスタマイズ¶
フォーム認証処理のカスタマイズポイントを説明する。
9.2.3.1.1. 認証パスの変更¶
Spring Securityのデフォルトでは、認証処理を実行するためのパスは「/login
」であるが、
以下のようなbean定義を行うことで変更することが可能である。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login login-processing-url="/authentication" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
login-processing-url 属性に認証処理を行うためのパスを指定する。 |
Note
認証処理のパスを変更した場合は、ログインフォーム のリクエスト先も変更する必要がある。
9.2.3.1.2. 資格情報を送るリクエストパラメータ名の変更¶
Spring Securityのデフォルトでは、資格情報(ユーザー名とパスワード)を送るためのリクエストパラメータは「username
」と「password
」であるが、
以下のようなbean定義を行うことで変更することが可能である。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login
username-parameter="uid"
password-parameter="pwd" /> <!-- (1) (2) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
username-parameter 属性にユーザー名のリクエストパラメータ名を指定する。 |
(2)
|
password-parameter 属性にパスワードのリクエストパラメータ名を指定する。 |
Note
リクエストパラメータ名を変更した場合は、ログインフォーム 内の項目名も変更する必要がある。
9.2.3.2. 認証成功時のレスポンスのカスタマイズ¶
認証成功時のレスポンスのカスタマイズポイントを説明する。
9.2.3.2.1. デフォルト遷移先の変更¶
ログインフォームを自分で表示して認証処理を行った後の遷移先(デフォルトURL)は、
Webアプリケーションのルートパス(“/
” )だが、以下のようなbean定義を行うことで変更することが可能である。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login default-target-url="/menu" /> <!-- (1) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
default-target-url 属性に認証成功時に遷移するデフォルトのパスを指定する。 |
9.2.3.2.2. 遷移先の固定化¶
Spring Securityのデフォルトの動作では、未認証時に認証が必要なページへのリクエストを受信した場合は、受信したリクエストを一旦HTTPセッションに保存し、認証ページに遷移する。 認証成功時にリクエストを復元してリダイレクトするが、以下のようなbean定義を行うことで常に同じ画面に遷移させることが可能である。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login
default-target-url="/menu"
always-use-default-target="true" /> <!-- (1) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
always-use-default-target 属性にtrue を指定する。 |
9.2.3.2.3. AuthenticationSuccessHandlerの適用¶
Spring Securityが提供しているデフォルトの動作をカスタマイズする仕組みだけでは要件をみたせない場合は、
以下のようなbean定義を行うことでAuthenticationSuccessHandler
インタフェースの実装クラスを直接適用することができる。
- spring-security.xmlの定義例
<bean id="authenticationSuccessHandler" class="com.example.app.security.handler.MyAuthenticationSuccessHandler"> <!-- (1) -->
<sec:http>
<sec:form-login authentication-success-handler-ref="authenticationSuccessHandler" /> <!-- (2) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
AuthenticationSuccessHandler インタフェースの実装クラスをbean定義する。 |
(2)
|
authentication-success-handler-ref 属性に定義したauthenticationSuccessHandler を指定する。 |
Warning
AuthenticationSuccessHandlerの責務
AuthenticationSuccessHandler
は、認証成功時におけるWeb層の処理(主に画面遷移に関する処理)を行うためのインタフェースである。
そのため、認証失敗回数のクリアなどのビジネスルールに依存する処理(ビジネスロジック)をこのインタフェースの実装クラスを経由して呼び出すべきではない。
ビジネスルールに依存する処理の呼び出しは、前節で紹介している「認証イベントのハンドリング」の仕組みを使用されたい。
9.2.3.3. 認証失敗時のレスポンスのカスタマイズ¶
認証失敗時のレスポンスのカスタマイズポイントを説明する。
9.2.3.3.1. 遷移先の変更¶
Spring Securityのデフォルトの動作では、ログインフォームを表示するためのパスにerror
というクエリパラメータが付与されたURLにリダイレクトするが、
以下のようなbean定義を行うことで変更することが可能である。
- spring-security.xmlの定義例
<sec:http>
<sec:form-login authentication-failure-url="/loginFailure" /> <!-- (1) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
authentication-failure-url 属性に認証失敗時に遷移するパスを指定する。 |
9.2.3.3.2. AuthenticationFailureHandlerの適用¶
Spring Securityが提供しているデフォルトの動作をカスタマイズする仕組みだけでは要件をみたせない場合は、
以下のようなbean定義を行うことでAuthenticationFailureHandler
インタフェースの実装クラスを直接適用することができる。
- spring-security.xmlの定義例
<!-- (1) -->
<bean id="authenticationFailureHandler"
class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" />
<property name="defaultFailureUrl" value="/login/systemError" /> <!-- (2) -->
<property name="exceptionMappings"> <!-- (3) -->
<props>
<prop key="org.springframework.security.authentication.BadCredentialsException"> <!-- (4) -->
/login/badCredentials
</prop>
<prop key="org.springframework.security.core.userdetails.UsernameNotFoundException"> <!-- (5) -->
/login/usernameNotFound
</prop>
<prop key="org.springframework.security.authentication.DisabledException"> <!-- (6) -->
/login/disabled
</prop>
<!-- omitted -->
</props>
</property>
</bean>
<sec:http>
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler" /> <!-- (7) -->
</sec:http>
項番
|
説明
|
---|---|
(1)
|
AuthenticationFailureHandler インタフェースの実装クラスをbean定義する。 |
(2)
|
defaultFailureUrl 属性にデフォルトの遷移先のURLを指定する。下記(4)-(6)の定義に合致しない例外が発生した際は、本設定の遷移先に遷移する。
|
(3)
|
exceptionMappings プロパティにハンドルするorg.springframework.security.authentication.AuthenticationServiceException の実装クラスと例外発生時の遷移先を Map 形式で設定する。キーに
org.springframework.security.authentication.AuthenticationServiceException 実装クラスを設定し、値に遷移先URLを設定する。 |
(4)
|
BadCredentialsException パスワード照合失敗による認証エラー時にスローされる。
|
(5)
|
UsernameNotFoundException 不正ユーザーID(存在しないユーザーID)による認証エラー時にスローされる。
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider を継承したクラスを認証プロバイダに指定している場合、
hideUserNotFoundExceptions プロパティをfalse に変更しないと本例外は、BadCredentialsException に変更される。 |
(6)
|
DisabledException 無効ユーザーIDによる認証エラー時にスローされる。
|
(7)
|
authentication-failure-handler-ref 属性にauthenticationFailureHandler を設定する。 |
Note
例外発生時の制御
exceptionMappings
プロパティに定義した例外が発生した場合、例外にマッピングした遷移先にリダイレクトされるが、
発生した例外オブジェクトがセッションスコープに格納されないため、Spring Securityが生成したエラーメッセージを画面に表示する事ができない。
そのため、遷移先の画面で表示するエラーメッセージは、リダイレクト先の処理(Controller又はViewの処理)で生成する必要がある。
また、以下のプロパティを参照する処理が呼び出されないため、設定値を変更しても動作が変わらないという点を補足しておく。
useForward
allowSessionCreation
9.2.3.4. ログアウト処理のカスタマイズ¶
ログアウト処理のカスタマイズポイントを説明する。
9.2.3.4.1. ログアウトパスの変更¶
Spring Securityのデフォルトでは、ログアウト処理を実行するためのパスは「/logout
」であるが、
以下のようなbean定義を行うことで変更することが可能である。
- spring-security.xmlの定義例
<sec:http>
<!-- omitted -->
<sec:logout logout-url="/auth/logout" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
logout-url 属性を設定し、ログアウト処理を行うパスを指定する。 |
Note
ログアウトパスを変更した場合は、ログアウトフォーム のリクエスト先も変更する必要がある。
Tip
システムエラー発生時の振る舞い システムエラーが発生した場合は、業務継続不可となるケースが多いと考えられる。 システムエラー発生後、業務を継続させたくない場合は、以下のような対策を講じることを推奨する。
- システムエラー発生時にセッション情報をクリアする。
- システムエラー発生時に認証情報をクリアする。
ここでは、共通ライブラリの例外ハンドリング機能を使用してシステム例外発生時に認証情報をクリアする例を説明する。 例外ハンドリング機能の詳細については「例外ハンドリング」を参照されたい。
// (1) public class LogoutSystemExceptionResolver extends SystemExceptionResolver { // (2) @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, java.lang.Object handler, java.lang.Exception ex) { // SystemExceptionResolverの処理を行う ModelAndView resulut = super.doResolveException(request, response, handler, ex); // 認証情報をクリアする (2) SecurityContextHolder.clearContext(); return resulut; } }
項番 説明 (1)org.terasoluna.gfw.web.exception.SystemExceptionResolver.SystemExceptionResolver
を拡張する。 (2) 認証情報をクリアする。
なお、認証情報をクリアする方法以外にも、セッションをクリアすることでも、同様の要件を満たすことができる。 プロジェクトの要件に合わせて実装されたい。
9.2.3.5. ログアウト成功時のレスポンスのカスタマイズ¶
ログアウト処理成功時のレスポンスのカスタマイズポイントを説明する。
9.2.3.5.1. 遷移先の変更¶
- spring-security.xmlの定義例
<sec:http>
<!-- omitted -->
<sec:logout logout-success-url="/logoutSuccess" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
logout-success-url 属性を設定し、ログアウト成功時に遷移するパスを指定する。 |
9.2.3.5.2. LogoutSuccessHandlerの適用¶
- spring-security.xmlの定義例
<!-- (1) -->
<bean id="logoutSuccessHandler" class="com.example.app.security.handler.MyLogoutSuccessHandler" />
<sec:http>
<!-- omitted -->
<sec:logout success-handler-ref="logoutSuccessHandler" /> <!-- (2) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
LogoutSuccessHandler インタフェースの実装クラスをbean定義する。 |
(2)
|
success-handler-ref 属性にLogoutSuccessHandler を設定する。 |
9.2.3.6. エラーメッセージのカスタマイズ¶
認証に失敗した場合、Spring Securityが用意しているエラーメッセージが表示されるが、 このエラーメッセージは変更することが可能である。
メッセージ変更方法の詳細については、メッセージ管理を参照されたい。
9.2.3.6.1. システムエラー時のメッセージ¶
認証処理の中で予期しないエラー(システムエラーなど)が発生した場合、InternalAuthenticationServiceException
という例外が発生する。
InternalAuthenticationServiceException
が保持するメッセージには、原因例外のメッセージが設定されるため、画面にそのまま表示するのは適切ではない。
例えばユーザー情報をデーターベースから取得する時にDBアクセスエラーが発生した場合、SQLException
が保持する例外メッセージが画面に表示されることになる。
システムエラーの例外メッセージを画面に表示させないためには、ExceptionMappingAuthenticationFailureHandler
を使用してInternalAuthenticationServiceException
をハンドリングし、
システムエラーが発生したことを通知するためのパスに遷移させるなどの対応が必要となる。
- spring-security.xmlの定義例
<bean id="authenticationFailureHandler"
class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login?error" />
<property name="exceptionMappings">
<props>
<prop key="org.springframework.security.authentication.InternalAuthenticationServiceException">
/login?systemError
</prop>
<!-- omitted -->
</props>
</property>
</bean>
<sec:http>
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler" />
</sec:http>
ここでは、システムエラーが発生したことを識別するためのクエリパラメータ(systemError
)を付けてログインフォームに遷移させている。
遷移先に指定したログインフォームでは、クエリパラメータにsystemError
が指定されている場合は、認証例外のメッセージを表示するのではなく、
固定のエラーメッセージを表示するようにしている。
- ログインフォームの実装例
<c:choose>
<c:when test="${param.containsKey('error')}">
<span style="color: red;">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>
</span>
</c:when>
<c:when test="${param.containsKey('systemError')}">
<span style="color: red;">
System Error occurred.
</span>
</c:when>
</c:choose>
Note
ここでは、ログインフォームに遷移させる場合の実装例を紹介したが、システムエラー画面に遷移させてもよい。
9.2.3.7. 認証時の入力チェック¶
DBサーバへの負荷軽減等で、認証ページにおける、あきらかな入力誤りに対しては、事前にチェックを行いたい場合がある。 このような場合は、Bean Validationを使用した入力チェックも可能である。
9.2.3.7.1. Bean Validationによる入力チェック¶
以下にBean Validationを使用した入力チェックの例を説明する。 Bean Validationに関する詳細は 入力チェックを参照すること。
- フォームクラスの実装例
public class LoginForm implements Serializable {
// omitted
@NotEmpty // (1)
private String username;
@NotEmpty // (1)
private String password;
// omitted
}
項番 | 説明 |
---|---|
(1)
|
本例では、
username 、password をそれぞれ必須入力としている。 |
- コントローラクラスの実装例
@ModelAttribute
public LoginForm setupForm() { // (1)
return new LoginForm();
}
@RequestMapping(value = "login")
public String login(@Validated LoginForm form, BindingResult result) {
// omitted
if (result.hasErrors()) {
// omitted
}
return "forward:/authenticate"; // (2)
}
項番 | 説明 |
---|---|
(1)
|
LoginForm を初期化する。 |
(2)
|
forwardで
<sec:form-login> 要素のlogin-processing-url 属性に指定したパスに Forward する。認証に関する設定は、フォーム認証のカスタマイズを参照すること。
|
加えて、Forwardによる遷移でもSpring Securityの処理が行われるよう、認証パスをSpring Securityサーブレットフィルタに追加する。
- web.xmlの設定例
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- (1) -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/authenticate</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
項番 | 説明 |
---|---|
(1)
|
Forwardで認証するためのパターンを指定する
ここでは認証パスである
/authenticate を指定している。 |
9.2.3.8. 認証処理の拡張¶
Spring Securityから提供されている認証プロバイダで対応できない認証要件がある場合は、
org.springframework.security.authentication.AuthenticationProvider
インタフェースを実装したクラスを作成する必要がある。
ここでは、ユーザー名、パスワード、会社識別子(独自の認証パラメータ)の3つのパラメータを使用してDB認証を行うための拡張例を示す。
上記の要件を実現するためには、以下に示すクラスを作成する必要がある。
項番 | 説明 |
---|---|
(1)
|
ユーザー名、パスワード、会社識別子を保持する
org.springframework.security.core.Authentication インタフェースの実装クラス。ここでは、
org.springframework.security.authentication.UsernamePasswordAuthenticationToken クラスを継承して作成する。 |
(2)
|
ユーザー名、パスワード、会社識別子を使用してDB認証を行う
org.springframework.security.authentication.AuthenticationProvider の実装クラス。ここでは、
org.springframework.security.authentication.dao.DaoAuthenticationProvider クラスを継承して作成する。 |
(3)
|
ユーザー名、パスワード、会社識別子をリクエストパラメータから取得して、
AuthenticationManager (AuthenticationProvider )に渡すAuthentication を生成するためのAuthentication Filterクラス。ここでは、
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter クラスを継承して作成する。 |
Note
ここでは、認証用のパラメータとして独自のパラメータを追加する例にしているため、
Authentication
インタフェースの実装クラスとAuthentication
を生成するためのAuthentication Filterクラスの拡張が必要となる。
ユーザー名とパスワードのみで認証する場合は、AuthenticationProvider
インタフェースの実装クラスを作成するだけで、
認証処理を拡張することができる。
9.2.3.8.1. Authenticationインタフェースの実装クラスの作成¶
UsernamePasswordAuthenticationToken
クラスを継承し、ユーザー名とパスワードに加えて、会社識別子(独自の認証パラメータ)を保持するクラスを作成する。
// import omitted
public class CompanyIdUsernamePasswordAuthenticationToken extends
UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// (1)
private final String companyId;
// (2)
public CompanyIdUsernamePasswordAuthenticationToken(
Object principal, Object credentials, String companyId) {
super(principal, credentials);
this.companyId = companyId;
}
// (3)
public CompanyIdUsernamePasswordAuthenticationToken(
Object principal, Object credentials, String companyId,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.companyId = companyId;
}
public String getCompanyId() {
return companyId;
}
}
項番 | 説明 |
---|---|
(1)
|
会社識別子を保持するフィールドを作成する。
|
(2)
|
認証前の情報(リクエストパラメータで指定された情報)を保持するインスタンスを作成する際に使用するコンストラクタを作成する。
|
(3)
|
認証済みの情報を保持するインスタンスを作成する際に使用するコンストラクタを作成する。
親クラスのコンストラクタの引数に認可情報を渡すことで、認証済みの状態となる。
|
9.2.3.8.2. AuthenticationProviderインタフェースの実装クラスの作成¶
DaoAuthenticationProvider
クラスを継承し、ユーザー名、パスワード、会社識別子を使用してDB認証を行うクラスを作成する。
// import omitted
public class CompanyIdUsernamePasswordAuthenticationProvider extends
DaoAuthenticationProvider {
// omitted
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// (1)
super.additionalAuthenticationChecks(userDetails, authentication);
// (2)
CompanyIdUsernamePasswordAuthenticationToken companyIdUsernamePasswordAuthentication =
(CompanyIdUsernamePasswordAuthenticationToken) authentication;
String requestedCompanyId = companyIdUsernamePasswordAuthentication.getCompanyId();
String companyId = ((SampleUserDetails) userDetails).getAccount().getCompanyId();
if (!companyId.equals(requestedCompanyId)) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
String companyId = ((SampleUserDetails) user).getAccount()
.getCompanyId();
// (3)
return new CompanyIdUsernamePasswordAuthenticationToken(user,
authentication.getCredentials(), companyId,
user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// (4)
return CompanyIdUsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
}
項番 | 説明 |
---|---|
(1)
|
親クラスのメソッドを呼び出し、Spring Securityが提供しているチェック処理を実行する。
この処理にはパスワード認証処理も含まれる。
|
(2)
|
パスワード認証が成功した場合は、会社識別子(独自の認証パラメータ)の妥当性をチェックする。
上記例では、リクエストされた会社識別子とテーブルに保持している会社識別子が一致するかをチェックしている。
|
(3)
|
パスワード認証及び独自の認証処理が成功した場合は、認証済み状態の
CompanyIdUsernamePasswordAuthenticationToken を作成して返却する。 |
(4)
|
CompanyIdUsernamePasswordAuthenticationToken にキャスト可能なAuthentication が指定された場合に、本クラスを使用して認証処理を行うようにする。 |
Note
ユーザーの存在チェック、ユーザーの状態チェック(無効ユーザー、ロック中ユーザー、利用期限切れユーザーなどのチェック)は、
additionalAuthenticationChecks
メソッドが呼び出される前に親クラスの処理として行われる。
9.2.3.8.3. Authentication Filterの作成¶
UsernamePasswordAuthenticationFilter
クラスを継承し、
認証情報(ユーザー名、パスワード、会社識別子)をAuthenticationProvider
に引き渡すためのAuthentication Filterクラスを作成する。
attemptAuthentication
メソッドの実装は、UsernamePasswordAuthenticationFilter
クラスのメソッドをコピーしてカスタマイズしたものである。
// import omitted
public class CompanyIdUsernamePasswordAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
// (1)
// Obtain UserName, Password, CompanyId
String username = super.obtainUsername(request);
String password = super.obtainPassword(request);
String companyId = obtainCompanyId(request);
if (username == null) {
username = "";
} else {
username = username.trim();
}
if (password == null) {
password = "";
}
CompanyIdUsernamePasswordAuthenticationToken authRequest =
new CompanyIdUsernamePasswordAuthenticationToken(username, password, companyId);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest); // (2)
}
// (3)
protected String obtainCompanyId(HttpServletRequest request) {
return request.getParameter("companyId");
}
}
項番 | 説明 |
---|---|
(1)
|
リクエストパラメータから取得した認証情報(ユーザー名、パスワード、会社識別子)より、
CompanyIdUsernamePasswordAuthenticationToken のインスタンスを生成する。 |
(2)
|
リクエストパラメータで指定された認証情報(
CompanyIdUsernamePasswordAuthenticationToken のインスタンス)を指定して、org.springframework.security.authentication.AuthenticationManager のauthenticate メソッドを呼び出す。AuthenticationManager のメソッドを呼び出すと、AuthenticationProvider の認証処理が呼び出される。 |
(3)
|
会社識別子は、
companyId というリクエストパラメータより取得する。 |
9.2.3.8.4. ログインフォームの修正¶
ログインフォームの作成で作成したログインフォーム(JSP)に対して、会社識別子を追加する。
<form:form action="${pageContext.request.contextPath}/login" method="post">
<!-- omitted -->
<tr>
<td><label for="username">User Name</label></td>
<td><input type="text" id="username" name="username"></td>
</tr>
<tr>
<td><label for="companyId">Company Id</label></td>
<td><input type="text" id="companyId" name="companyId"></td> <!-- (1) -->
</tr>
<tr>
<td><label for="password">Password</label></td>
<td><input type="password" id="password" name="password"></td>
</tr>
<!-- omitted -->
</form:form>
項番 | 説明 |
---|---|
(1)
|
会社識別子の入力フィールド名に
companyId を指定する。 |
9.2.3.8.5. 拡張した認証処理の適用¶
ユーザー名、パスワード、会社識別子(独自の認証パラメータ)を使用したDB認証機能をSpring Securityに適用する。
- spring-security.xmlの定義例
<!-- omitted -->
<!-- (1) -->
<sec:http
entry-point-ref="loginUrlAuthenticationEntryPoint">
<!-- omitted -->
<!-- (2) -->
<sec:custom-filter
position="FORM_LOGIN_FILTER" ref="companyIdUsernamePasswordAuthenticationFilter" />
<!-- omitted -->
<sec:csrf token-repository-ref="csrfTokenRepository" />
<sec:logout
logout-url="/logout"
logout-success-url="/login" />
<!-- omitted -->
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<!-- omitted -->
</sec:http>
<!-- (3) -->
<bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/login" />
</bean>
<!-- (4) -->
<bean id="companyIdUsernamePasswordAuthenticationFilter"
class="com.example.app.common.security.CompanyIdUsernamePasswordAuthenticationFilter">
<!-- (5) -->
<property name="requiresAuthenticationRequestMatcher">
<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg index="0" value="/authentication" />
<constructor-arg index="1" value="POST" />
</bean>
</property>
<!-- (6) -->
<property name="authenticationManager" ref="authenticationManager" />
<!-- (7) -->
<property name="sessionAuthenticationStrategy" ref="sessionAuthenticationStrategy" />
<!-- (8) -->
<property name="authenticationFailureHandler">
<bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<constructor-arg value="/login?error=true" />
</bean>
</property>
<!-- (9) -->
<property name="authenticationSuccessHandler">
<bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler" />
</property>
</bean>
<!-- (6') -->
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="companyIdUsernamePasswordAuthenticationProvider" />
</sec:authentication-manager>
<bean id="companyIdUsernamePasswordAuthenticationProvider"
class="com.example.app.common.security.CompanyIdUsernamePasswordAuthenticationProvider">
<property name="userDetailsService" ref="sampleUserDetailsService" />
<property name="passwordEncoder" ref="passwordEncoder" />
</bean>
<!-- (7') -->
<bean id="sessionAuthenticationStrategy"
class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<constructor-arg>
<util:list>
<bean class="org.springframework.security.web.csrf.CsrfAuthenticationStrategy">
<constructor-arg ref="csrfTokenRepository" />
</bean>
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
</util:list>
</constructor-arg>
</bean>
<bean id="csrfTokenRepository"
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository" />
<!-- omitted -->
項番 | 説明 |
---|---|
(1)
|
(2)の
<sec:custom-filter> タグを使用してFORM_LOGIN_FILTER を差し替える場合は、<sec:http> タグの属性に以下の設定を行う必要がある。
|
(2)
|
<sec:custom-filter> タグを使用してFORM_LOGIN_FILTER を差し替える。<sec:custom-filter> タグのposition 属性にFORM_LOGIN_FILTER を指定し、ref 属性に拡張したAuthentication Filterのbeanを指定する。 |
(3)
|
<sec:http> タグのentry-point-ref 属性に使用するAuthenticationEntryPoint のbeanを指定する。ここでは、
<sec:form-login> タグを指定した際に使用されるorg.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint クラスのbeanを指定している。 |
(4)
|
FORM_LOGIN_FILTER として使用するAuthentication Filterクラスのbeanを定義する。ここでは、拡張したAuthentication Filterクラス(
CompanyIdUsernamePasswordAuthenticationFilter )のbeanを定義している。 |
(5)
|
requiresAuthenticationRequestMatcher プロパティに、認証処理を行うリクエストを検出するためのRequestMatcher インスタンスを指定する。ここでは、
/authentication というパスにリクエストがあった場合に認証処理を行うように設定している。これは、
<sec:form-login> タグのlogin-processing-url 属性に/authentication を指定したのと同義である。 |
(6)
|
authenticationManager プロパティに、<sec:authentication-manager> タグのalias 属性に設定した値を指定する。<sec:authentication-manager> タグのalias 属性を指定すると、Spring Securityが生成した
AuthenticationManager のbeanを、他のbeanへDIすることができる様になる。 |
(6’)
|
Spring Securityが生成する
AuthenticationManager に対して、拡張したAuthenticationProvider (CompanyIdUsernamePasswordAuthenticationProvider )を設定する。 |
(7)
|
sessionAuthenticationStrategy プロパティに、認証成功時のセッションの取り扱いを制御するコンポーネント(SessionAuthenticationStrategy )のbeanを指定する。 |
(7’)
|
認証成功時のセッションの取り扱いを制御するコンポーネント(
SessionAuthenticationStrategy )のbeanを定義する。ここでは、Spring Securityから提供されている、
を有効化している。
|
(8)
|
authenticationFailureHandler プロパティに、認証失敗時に呼ばれるハンドラクラスを指定する。 |
(9)
|
authenticationSuccessHandler プロパティに、認証成功時に呼ばれるハンドラクラスを指定する。 |
Note
auto-configについて
auto-config="false"
を指定又は指定を省略した際にBasic認証処理とログアウト処理を有効化したい場合は、<sec:http-basic>
タグと<sec:logout>
タグを明示的に定義する必要がある。
9.2.3.9. PasswordEncoderのカスタマイズ¶
DelegatingPasswordEncoder
で使用されるPasswordEncoder
インタフェースの実装は全てデフォルトの設定であるが、システムの要件によってはカスタマイズを行う必要がある。
ここではPasswordEncoder
インタフェースの実装のカスタマイズ方法を紹介する。
Note
OWASP(Open Web Application Security Project)ではハッシュ関数に使用するイテレーションカウント(ハッシュ化の繰返し回数)について、ユーザに影響を与えない範囲で出来る限り攻撃を対策できる値を設定することが推奨されている。 ハードウェアの処理速度に比例してユーザに影響を与えない範囲が変化するため、実際に設定すべきイテレーションカウントは環境によって異なる。
9.2.3.9.1. Pbkdf2PasswordEncoderのカスタマイズ¶
Pbkdf2PasswordEncoder
では、ソルト に8バイトの乱数(java.security.SecureRandom
)が使用され、
イテレーション カウントはデフォルトでは185,000回に設定されている。
ここではイテレーションカウントの変更方法を紹介する。
applicationContext.xml
を以下のように変更する。
- applicationContext.xmlの変更
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder">
<constructor-arg name="idForEncode" value="pbkdf2" />
<constructor-arg name="idToPasswordEncoder">
<map>
<entry key="pbkdf2">
<bean class="org.springframework.security.crypto.password.Pbkdf2PasswordEncoder"> <!-- (1) -->
<constructor-arg name="secret" value="" /> <!-- (2) -->
<constructor-arg name="iterations" value="1000000" /> <!-- (3) -->
<constructor-arg name="hashWidth" value="256" /> <!-- (4) -->
</bean>
</entry>
<!-- omitted -->
</map>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
Pbkdf2PasswordEncoder をカスタマイズするため、使用するコンストラクタをデフォルトコンストラクタから変更するコンストラクタに指定する引数については以下で解説を行う。
|
(2)
|
secret にはハッシュ化で使用する秘密鍵を指定する。デフォルトは空(””)である。
|
(3)
|
iterations にはハッシュ化のiterations(反復)回数を指定する。ここでは1,000,000回を指定する。
デフォルトでは185000が設定されている。
|
(4)
|
hashWidth には出力されるハッシュ値の長さを指定する。デフォルトでは256が設定されている。
|
Warning
SecureRandomの使用について
Linux環境でSecureRandom
を使用する場合、処理の遅延やタイムアウトが発生する場合がある。
これは使用する乱数生成器に左右される事象であり、以下のドキュメントに説明がある。
本事象が発生する場合は、以下のいずれかの設定を追加することで回避することができる。
- Javaコマンド実行時に
-Djava.security.egd=file:/dev/urandom
を指定する。 ${JAVA_HOME}/jre/lib/security/java.security
内のsecurerandom.source=/dev/random
をsecurerandom.source=/dev/urandom
に変更する。
Note
ソルト
ハッシュ化対象のデータに追加する文字列のことである。 ソルトをパスワードに付与することで、実際のパスワードより桁数が長くなるため、レインボークラックなどのパスワード解析を困難にすることができる。 なお、ソルトはユーザーごとに異なる値(ランダム値等)を設定することを推奨する。 これは、同じソルトを使用していると、ハッシュ値からハッシュ化前の文字列(パスワード)がわかってしまう可能性があるためである。
Note
イテレーション
ハッシュ関数の計算を繰り返し行うことで、保管するパスワードに関する情報を繰り返し暗号化することである。 パスワードの総当たり攻撃への対策として、パスワード解析に必要な時間を延ばすために行う。 しかし、イテレーションはシステムの性能に影響を与えるので、システムの性能を考慮してイテレーションカウントを決める必要がある。
Spring Securityのデフォルトでは185,000回イテレーションを行うが、この回数はコンストラクタ引数(iterations
)で変更することができる。
イテレーションカウントが多いほどパスワードの強度は増すが、計算量が多くなるため性能にあたえる影響も大きくなる。
9.2.3.10. 非推奨アルゴリズムのPasswordEncoderの利用¶
セキュリティ要件によっては、前述したPasswordEncoder
を実装したクラスでは実現できない場合がある。
特に、既存のアカウント情報で使用しているハッシュ化要件を踏襲する必要がある場合は、前述のPasswordEncoder
では要件を満たせないことがある。
具体的には、既存のハッシュ化要件が以下のようなケースである。
- アルゴリズムがSHA-512である。
- ハッシュ化回数が1000回である。
このようなケースでは、PasswordEncoder
の実装クラスの一つであるMessageDigestPasswordEncoder
を利用することで要件を満たすことができる。
Warning
Spring Security 4では、Spring Security 3.1.4で非推奨になったorg.springframework.security.authentication.encoding.PasswordEncoder
を実装したクラスをハッシュ化に使用していたが、Spring Security 5では廃止されている。
9.2.3.10.1. MessageDigestPasswordEncoderの利用¶
MessageDigestPasswordEncoder
はJavaが提供するjava.security.MessageDigest
クラスを利用してハッシュ化を行う。
MessageDigest
クラスはMD-5、SHA-1、SHA-256等のハッシュアルゴリズムを提供している。
詳細についてはJavadocを参照されたい。
Warning
MessageDigestPasswordEncoder
は旧式の実装であることを示すため非推奨となっている。
このクラスが廃止される予定はないが、セキュリティ上のリスクが想定されるため、Pbkdf2アルゴリズムやBCryptアルゴリズムを利用することを検討されたい。
本ガイドラインでは、DelegatingPassowordEncoder
を通してMessageDigestPasswordEncoder
を利用する方法について説明する。
ここでは以下のハッシュ化要件を満たすPasswordEncoder
を実装する。
- アルゴリズムがSHA-512である。
- ハッシュ化回数が1000回である。
Note
MessageDigestPasswordEncoderの仕様
MessageDigestPasswordEncoder
でハッシュ化を行うと以下のフォーマットで出力される。
{salt
}hashValue
ソルトはSpring Security 5からランダムに生成するようになったため、安全性が向上している。
ハッシュ化したパスワードに付与されたソルトは照合の際に使用される。
既に固定のソルトを用いてハッシュ化したパスワードについて
先述の通り、Spring Security 5のMessageDigestPasswordEncoder
はソルトをランダムに生成するが、
既に固定のソルトを用いてパスワードをハッシュ化していた場合も、パスワードにソルトを付与する移行処理を行うことで、
照合することができるようになる。
パスワードデータの移行については、MessageDigestPasswordEncoderのJavadocを参照されたい。
この場合、既存のパスワードは固定のソルトを用いて照合が行われるが、パスワードを新規に設定または変更した場合はランダムなソルトが用いられる。
Warning
MessageDigestPasswordEncoder
を使用する際は、以下の点に注意する必要がある。
- 既存のハッシュ値より文字数が多くなる
- エンコードしたパスワードからソルトの情報が得られる
まず、MessageDigestPasswordEncoder
でハッシュ化を行うDelegatingPasswordEncoder
のbeanを定義する。
- applicationContext.xmlの定義例
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <!-- (1) -->
<constructor-arg name="idForEncode" value="MD" />
<constructor-arg name="idToPasswordEncoder">
<map>
<entry key="MD">
<bean class="org.springframework.security.crypto.password.MessageDigestPasswordEncoder"> <!-- (2) -->
<constructor-arg name="algorithm" value="SHA-512" /> <!-- (3) -->
<property name="iterations" value="1000" /> <!-- (4) -->
</bean>
</entry>
<!-- omitted -->
</map>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
ここではbeanのidに
passwordEncoder を指定し、sec:authentication-provider 配下に自動的に参照されるように設定する。 |
(2)
|
ハッシュ化に使用する
PasswordEncoder としてMessageDigestPasswordEncoder をbean定義する。 |
(3)
|
MessageDigestPasswordEncoder で使用するハッシュアルゴリズムを指定する。ここには
MessageDigest クラスが対応するアルゴリズムを指定することができる。指定できる値については、Java暗号化アーキテクチャ標準アルゴリズム名のドキュメント を参照されたい。
|
(4)
|
ハッシュ化のiterations(反復)回数を指定する。
設定を行わなかった場合、1回となる。
|
Warning
実際のアプリケーション開発では、セキュリティ上のリスクを軽減するためidForEncode
とidToPasswordEncoder
のkey
値にアルゴリズム名を推測できないような値を指定することを推奨する。
9.2.4. Appendix¶
9.2.4.1. Spring MVCでリクエストを受けてログインフォームを表示する¶
Spring MVCでリクエストを受けてログインフォームを表示する方法を説明する。
- ログインフォームを表示するControllerの定義例
@Controller
@RequestMapping("/login")
public class LoginController { // (1)
@RequestMapping
public String index() {
return "login";
}
}
項番 | 説明 |
---|---|
(1)
|
view名として”login”を返却する。
InternalResourceViewResolver によってsrc/main/webapp/WEB-INF/views/login.jspが出力される。 |
本例のように、単純にview名を返すだけのメソッドが一つだけあるControllerであれば、<mvc:view-controller>
を使用して代用することも可能である。
詳しくは、HTMLを応答するを参照されたい。
9.2.4.2. Remember Me認証の利用¶
「Remember Me認証」とは、 Webサイトに頻繁にアクセスするユーザーの利便性を高めるための機能の一つで、ログイン状態を通常のライフサイクルより長く保持するための機能である。 本機能を使用すると、ブラウザを閉じた後やセッションタイムが発生した後でも、Cookieに保持しているRemember Me認証用のTokenを使用して、 ユーザ名とパスワードを再入力することなく自動でログインすることができる。 なお、本機能は、ユーザーがログイン状態を保持することを許可した場合のみ有効となる。
Spring Securityは、「Hash-Based Token 方式のRemember Me認証」と「Persistent Token 方式のRemember Me認証」をサポートしており、 デフォルトではHash-Based Token方式が使用される。
Remember Me認証を利用する場合は、<sec:remember-me>
タグを追加する。
- spring-security.xmlの定義例
<sec:http>
<!-- omitted -->
<sec:remember-me key="terasoluna-tourreservation-km/ylnHv"
token-validity-seconds="#{30 * 24 * 60 * 60}" /> <!-- (1) (2) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
key 属性に、Remember Me認証用のTokenを生成したアプリケーションを識別するキー値を指定する。キー値の指定が無い場合、アプリケーションの起動毎にユニークな値が生成される。
なお、Hash-Based Tokenが保持しているキー値とサーバーで保持しているキー値が異なる場合、無効なTokenとして扱われる。
つまり、アプリケーションを再起動する前に生成したHash-Based Tokenを有効なTokenとして扱いたい場合は、
key 属性の指定は必須である。 |
(2)
|
token-validity-seconds 属性に、Remember Me認証用のTokenの有効時間を秒単位で指定する。指定が無い場合、デフォルトで14日間が有効時間になる。
上記例では、有効時間として30日間を設定している。
|
上記以外の属性については、Spring Security Reference -The Security Namespace (<remember-me>) -を参照されたい。
Note
Spring Security 4.0における変更
Spring Security 4.0から、以下の設定のデフォルト値が変更されている
- remember-me-parameter
- remember-me-cookie
ログインフォームには、「Remember Me認証」機能の利用有無を指定するためのフラグ(チェックボックス項目)を用意する。
- ログインフォームのJSPの実装例
<form:form action="${pageContext.request.contextPath}/login" method="post">
<!-- omitted -->
<tr>
<td><label for="remember-me">Remember Me : </label></td>
<td><input name="remember-me" id="remember-me" type="checkbox" checked="checked" value="true"></td> <!-- (1) -->
</tr>
<!-- omitted -->
</form:form>
項番 | 説明 |
---|---|
(1)
|
「Remember Me認証」機能の利用有無を指定するためのフラグ(チェックボックス項目)を追加し、フィールド名(リクエストパラメータ名)には、
remember-me-parameter のデフォルト値であるremember-me を指定する。チェックボックスの
value 属性には、true を設定する。チェックボックスをチェック状態にしてから認証処理を実行すると、以降のリクエストから「Remember Me認証」機能が適用される。
|
Tip
value属性の設定値について
value
属性には、true
を設定する旨がrememberMeRequestedのJavaDocに記載されているが、
実装上はon
、yes
、”1
” も設定可能である。