9.2. 認証¶
目次
- Overview
- How to use
- How to extend
- Appendix
9.2.1. Overview¶
本節では、Spring Securityが提供している認証機能について説明する。
認証処理は、アプリケーションを利用するユーザーの正当性を確認するための処理である。
Note
ブランクプロジェクトではデフォルトで<sec:form-login/>
タグと<sec:logout/>
タグが設定されており、Spring Securityのフォーム認証が有効となっている。
フォーム認証を使用しない場合は、これらのタグを削除する必要がある。削除しない場合、ユーザから/login /logoutに対するリクエストがあると、想定外の認証処理が実行される可能性がある。
9.2.1.1. 認証処理のアーキテクチャ¶
Spring Securityは、以下のような流れで認証処理を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントは、認証処理を行うパスに対して資格情報(ユーザー名とパスワード)を指定してリクエストを送信する。
|
(2)
|
Authentication Filterは、リクエストから資格情報を取得して、
AuthenticationManager クラスの認証処理を呼び出す。 |
(3)
|
ProviderManager (デフォルトで使用されるAuthenticationManager の実装クラス)は、実際の認証処理をAuthenticationProvider インタフェースの実装クラスに委譲する。 |
9.2.1.1.1. Authentication Filter¶
クラス名 | 説明 |
---|---|
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
は、認証処理を実行するためのインタフェースである。ProviderManager
)では、実際の認証処理はAuthenticationProvider
に委譲し、AuthenticationProvider
で行われた認証処理の処理結果をハンドリングする仕組みになっている。9.2.1.1.3. AuthenticationProvider¶
AuthenticationProvider
は、認証処理の実装を提供するためのインタフェースである。AuthenticationProvider
の実装クラスは以下の通り。クラス名 | 説明 |
---|---|
DaoAuthenticationProvider |
データストアに登録しているユーザーの資格情報とユーザーの状態をチェックして認証処理を行う実装クラス。
チェックで必要となる資格情報とユーザーの状態は
UserDetails というインタフェースを実装しているクラスから取得する。 |
RememberMeAuthenticationProvider |
Remember Me認証用のTokenを検証する
AuthenticationProvider の実装クラス。 |
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 request-matcher="ant">
<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. ログインフォームの作成¶
- ログインフォームを表示するためのThymeleafでの作成例(xxx-web/src/main/webapp/WEB-INF/views/login/loginForm.html)
<html xmlns:th="http://www.thymeleaf.org">
<!--/* omitted */-->
<div id="wrapper">
<h3>Login Screen</h3>
<!--/* (1) */-->
<div th:if="${param.keySet().contains('error')}"
th:with="exception=${SPRING_SECURITY_LAST_EXCEPTION} ?: ${session[SPRING_SECURITY_LAST_EXCEPTION]}">
<div th:if="${exception != null}" class="alert alert-error">
<span th:text="${exception.message}"></span><!--/* (2) */-->
</div>
</div>
<form th:action="@{/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>
</div>
<!--/* omitted */-->
項番 | 説明 |
---|---|
(1)
|
認証エラーを表示するためのエリア。
|
(2)
|
認証エラー時に出力させる例外メッセージを出力する。
なお、認証エラーが発生した場合は、セッション又はリクエストスコープに
SPRING_SECURITY_LAST_EXCEPTION という属性名で例外オブジェクトが格納される。 |
(3)
|
ユーザー名とパスワードを入力するためのログインフォーム。
ここではユーザー名を
username 、パスワードをpassowrd というリクエストパラメータで送信する。また、
th:action 属性を使用することで、CSRF対策用のトークン値がリクエストパラメータで送信される。CSRF対策については、「CSRF対策」で説明する。
|
Warning
リクエストパラメータの存在チェックについて
Tutorial: Using Thymeleaf -Web context namespaces for request/session attributes, etc.-では、リクエストパラメータの存在チェック方法についてparam.containsKey
が紹介されているが、org.thymeleaf.context.WebEngineContext.RequestParametersMap
の実装により、param.containsKey
は一律trueが返却されるため、param.keySet().contains
を使用してパラメータの有無を判断する必要がある。なお、リクエストパラメータ以外にセッション(session.containsKey
)、サーブレットコンテキスト(application.containsKey
)についても同様である。
つぎに、作成したログインフォームをSpring Securityに適用する。
- spring-security.xmlの定義例
<sec:http request-matcher="ant">
<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リソースに対してアクセスポリシーの指定方法については、「認可」を参照されたい。
|
9.2.2.2. 認証成功時のレスポンス¶
Spring Securityは、認証成功時のレスポンスを制御するためのコンポーネントとして、AuthenticationSuccessHandler
というインタフェースと実装クラスを提供している。
実装クラス | 説明 |
---|---|
SavedRequestAwareAuthenticationSuccessHandler |
認証前にアクセスを試みたURLにリダイレクトを行う実装クラス。
デフォルトで使用される実装クラス。
|
SimpleUrlAuthenticationSuccessHandler |
defaultTargetUrl にリダイレクト又はフォワードを行う実装クラス。 |
9.2.2.2.1. デフォルトの動作¶
SavedRequestAwareAuthenticationSuccessHandler
クラスである。/
” )となっているため、認証成功時は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メソッドを用意する。
|
Note
UserDetails実装クラスのequalsメソッドについて
UserDetails
を実装する際に、equals
メソッドを実装しない場合はObject
の比較となる。
そのため、要件によってはequals
メソッドを実装する必要がある。例として、Spring Securityの提供するUser
クラスでは、username
が一致するかを確認している。
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.findByUsername(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は、passwordEncoder
という名前のBeanを定義していると、sec:authentication-provider
配下にsec:password-encoder
要素を指定しない場合に自動的に参照する。これにより、sec:password-encoder
の指定を省略することが可能である。
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
org.springframework.security.crypto.password.PasswordEncoderのメソッド定義
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
メソッド名 | 説明 |
---|---|
encode |
パスワードをハッシュ化するためのメソッド。
アカウントの登録処理やパスワード変更処理などでデータストアに保存するパスワードをハッシュ化する際に使用できる。
|
matches |
平文のパスワードとハッシュ化されたパスワードを照合するためのメソッド。
このメソッドはSpring Securityの認証処理でも利用されるが、パスワード変更処理などで現在のパスワードや過去に使用していたパスワードと照合する際にも使用できる。
|
upgradeEncoding |
ハッシュ化されたパスワードをセキュリティ強化のために再度ハッシュ化する必要があるか検証するためのメソッド。
本メソッドは、DB等から取得したハッシュ化されたパスワードを認証情報として保持する際に、セキュリティ強度の低いハッシュの漏洩を防止するために利用され、主にフレームワーク内部で利用されるメソッドである。
|
PasswordEncoder
インタフェースの実装クラスとして以下の3つのいずれかを使用することを推奨している。DelegatingPassowordEncoder
を通して使用することを推奨する。実装クラス | 説明 |
---|---|
Pbkdf2PasswordEncoder |
PBKDF2アルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
本ガイドラインでは、このクラスを使用することを推奨している。
詳細は、Pbkdf2PasswordEncoderのJavaDocを参照されたい。
|
BCryptPasswordEncoder |
BCryptアルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
詳細は、BCryptPasswordEncoderのJavaDocを参照されたい。
|
Argon2PasswordEncoder |
Argon2アルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
詳細は、Argon2PasswordEncoderのJavaDocを参照されたい。
|
SCryptPasswordEncoder |
SCryptアルゴリズムを使用してパスワードのハッシュ化及び照合を行う実装クラス。
詳細は、SCryptPasswordEncoderのJavaDocを参照されたい。
|
Note
OWASP(Open Web Application Security Project)ではFIPSに準ずるPBKDF2アルゴリズムが推奨されている。
ブランクプロジェクトが提供するPasswordEncoder
の定義も、デフォルトでPbkdf2PasswordEncoder
を使用する定義となっている。
Note
Argon2PasswordEncoder
またはSCryptPasswordEncoder
を使用する場合は、ブランクプロジェクトのデフォルト設定から変更する必要がある。
applicationContext.xml
のコメントアウトを外し、SCryptPasswordEncoder
の定義を有効化する。
applicationContext.xml
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <constructor-arg name="idForEncode" value="pbkdf2@SpringSecurity_v5_8" /> <constructor-arg name="idToPasswordEncoder"> <map> <!-- ommited --> <!-- When using commented out PasswordEncoders, you need to add bcprov-jdk18on.jar to the dependency. <entry key="argon2@SpringSecurity_v5_8"> <bean class="org.springframework.security.crypto.argon2.Argon2PasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" /> </entry> <entry key="scrypt@SpringSecurity_v5_8"> <bean class="org.springframework.security.crypto.scrypt.SCryptPasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" /> </entry> --> </map> </constructor-arg> </bean>
依存ライブラリとして不足しているbcprov-jdk18on
を追加する。
pom.xmlに以下のdependencyを追加すれば良い。
pom.xml
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> </dependency>
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
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 が付与されているためPbkdf2PasswordEncoder で照合が行われる。 |
ブランクプロジェクトではPbkdf2PasswordEncoder
を使用するDelegatingPasswordEncoder
が定義されている。
ここではブランクプロジェクトで定義されているPasswordEncoder
をもとに解説を行う。
- applicationContext.xmlの定義
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <!-- (1) -->
<constructor-arg name="idForEncode" value="pbkdf2@SpringSecurity_v5_8" /> <!-- (2) -->
<constructor-arg name="idToPasswordEncoder"> <!-- (3) -->
<map> <!-- (4) -->
<entry key="pbkdf2@SpringSecurity_v5_8">
<bean class="org.springframework.security.crypto.password.Pbkdf2PasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" />
</entry>
<entry key="bcrypt">
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
</entry>
<!-- When using commented out PasswordEncoders, you need to add bcprov-jdk18on.jar to the dependency.
<entry key="argon2@SpringSecurity_v5_8">
<bean class="org.springframework.security.crypto.argon2.Argon2PasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" />
</entry>
<entry key="scrypt@SpringSecurity_v5_8">
<bean class="org.springframework.security.crypto.scrypt.SCryptPasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" />
</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のカスタマイズについても参照し、セキュリティ要件を満たすように変更を加えられたい。
Note
Pbkdf2PasswordEncoder
、Argon2PasswordEncoder
、SCryptPasswordEncoder
はファクトリーメソッドとしてdefaultsForSpringSecurity_v5_8
を使用している。
Pbkdf2PasswordEncoder
の場合、以下でハッシュ化が実行される。業務要件に応じ適切に変更されたい。
- アルゴリズム : SHA-256
- ソルト : 16 バイト
- 反復回数 : 310,000 回
カスタマイズ方法は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@SpringSecurity_v5_8
)に指定すれば良い。
Warning
既存のアプリケーションにDelegatingPasswordEncoderを適用する際の注意点
PasswordEncoderに関する注意点
既に運用しているシステムでは、パスワードにプレフィックスは付与されていない。
プレフィックスが付与されていないパスワードをそのまま照合に使用するには、以下で示すようにdefaultPasswordEncoderForMatches
プロパティで照合に使用するエンコーダを指定する必要がある。
applicationContext.xmlの変更
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.DelegatingPasswordEncoder"> <constructor-arg name="idForEncode" value="pbkdf2@SpringSecurity_v5_8" /> <constructor-arg name="idToPasswordEncoder"> <map> <entry key="pbkdf2@SpringSecurity_v5_8"> <bean class="org.springframework.security.crypto.password.Pbkdf2PasswordEncoder" factory-method="defaultsForSpringSecurity_v5_8" /> </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 が付与されているメソッドを呼び出してイベントを通知する。 |
9.2.2.6.1. 認証成功イベント¶
イベントクラス | 説明 |
---|---|
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コンテナに登録する。
- イベントリスナクラスの実装例
package com.examples.domain.common.event; // (1)
@Component // (1)
public class AuthenticationEventListeners {
private static final Logger logger =
LoggerFactory.getLogger(AuthenticationEventListeners.class);
@EventListener(AuthenticationFailureBadCredentialsEvent.class) // (2)
public void handleBadCredentials(
AuthenticationFailureBadCredentialsEvent event) { // (3)
logger.info("Bad credentials is detected. username : {}", event.getAuthentication().getName());
// omitted
}
項番 | 説明 |
---|---|
(1)
|
コンポーネントスキャン機能を利用してイベントリスナクラスを登録するため、
@Component をクラスに付与する。Warning イベントリスナクラスの配置について Spring Securityが参照するWebアプリケーション用のアプリケーションコンテキストに登録するため、アプリケーションの ただし、 このイベントをハンドリングする場合はwebモジュール( |
(2)
|
@EventListener をメソッドに付与したメソッドを作成する。イベントリスナは属性値に指定された認証イベントクラスを処理する。認証イベントクラスは複数指定することができる。
|
(3)
|
メソッドの引数にハンドリングしたい認証イベントクラスを指定する。
|
上記例では、クライアントが指定した認証情報に誤りがあった場合に通知されるAuthenticationFailureBadCredentialsEvent
をハンドリングするクラスを作成する例としているが、他のイベントも同じ要領でハンドリングすることが可能である。
Tip
総当たり攻撃による不正ログインの兆候を検出するための方法として、ログイン認証時のログを監視することがあげられる。
実装例のようなAuthenticationFailureBadCredentialsEvent
をハンドリングするイベントリスナを作成して認証情報の誤りをログ情報として出力することで、Spring Securityを使用した認証時のログを監視することが可能になる。
9.2.2.7. ログアウト¶
Spring Securityは、以下のような流れでログアウト処理を行う。
項番 | 説明 |
---|---|
(1)
|
クライアントは、ログアウト処理を行うためのパスにリクエストを送信する。
|
(2)
|
LogoutFilter は、LogoutHandler のメソッドを呼び出し、実際のログアウト処理を行う。 |
(3)
|
LogoutFilter は、LogoutSuccessHandler のメソッドを呼び出し、画面遷移を行う。 |
LogoutHandler
の実装クラスは複数存在し、それぞれ以下の役割をもっている。
実装クラス | 説明 |
---|---|
SecurityContextLogoutHandler |
ログインユーザーの認証情報のクリアとセッションの破棄を行うクラス。
|
CookieClearingLogoutHandler |
指定したクッキーを削除するためのレスポンスを行うクラス。
|
CsrfLogoutHandler |
CSRF対策用トークンの破棄を行うクラス。
|
LogoutSuccessEventPublishingLogoutHandler |
LogoutSuccessEvent クラスのインスタンスを生成し、ApplicationEventPublisher に渡してイベントの通知依頼を行うクラス。 |
LogoutHandler
は、Spring Securityが提供しているbean定義をサポートするクラスが自動でLogoutFilter
に設定する仕組みになっているため、基本的にはアプリケーションの開発者が直接意識する必要はない。LogoutHandler
の実装クラスも設定される。Note
Clear-Site-Dataヘッダの付与
Spring Securityでは、Webサイトの閲覧用データ(クッキー、ストレージ、キャッシュ)を削除するためのClear-Site-Dataヘッダを付与するorg.springframework.security.web.header.writers.ClearSiteDataHeaderWriter
を提供している。
本機能はLogoutHandler
の仕組みを用いて適用されるが、自動的には適用されない。
適用するにはLogoutFilter
をbean定義し、org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler
を用いて登録する必要がある。
9.2.2.7.1. ログアウト処理の適用¶
ログアウト処理を適用するためには、以下のようなbean定義を行う。
- spring-security.xmlの定義例
<sec:http request-matcher="ant">
<!-- omitted -->
<sec:logout /> <!-- (1) -->
<!-- omitted -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
<sec:logout> タグを定義することで、ログアウト処理が有効となる。 |
Tip
Cookieの削除
本ガイドラインでは説明を割愛するが、 <sec:logout>
タグには、ログアウト時に指定したCookieを削除するためのdelete-cookies
属性が存在する。ただし、この属性を使用しても正常にCookieが削除できないケースが報告されている。
詳細はSpring Securityの以下のJIRAを参照されたい。
9.2.2.7.2. デフォルトの動作¶
/logout
というパスにリクエストを送るとログアウト処理が行われる。また、
- CSRF対策を行っている場合は、「CSRF対策用トークンの破棄」
- Remember Me認証機能を使用している場合は、「Remember Me認証用のTokenの破棄」
も行われる
- ログアウト処理を呼び出すためのThymeleafでの実装例
<html xmlns:th="http://www.thymeleaf.org">
<!--/* omitted */-->
<form th:action="@{/logout}" method="post"> <!--/* (1) */-->
<button>ログアウト</button>
</form>
項番 | 説明 |
---|---|
(1)
|
Note
CSRFトークンの送信
CSRF対策を有効にしている場合は、CSRF対策用のトークンをPOSTメソッドで送信する必要がある。
9.2.2.8. ログアウト成功時のレスポンス¶
Spring Securityは、ログアウト成功時のレスポンスを制御するためのコンポーネントとして、
LogoutSuccessHandler
というインタフェースと実装クラスを提供している。
実装クラス | 説明 |
---|---|
SimpleUrlLogoutSuccessHandler |
指定したパス(
defaultTargetUrl )にリダイレクトを行う実装クラス。 |
HttpStatusReturningLogoutSuccessHandler |
ログアウト成功時のレスポンスに任意のステータスコードを設定する実装クラス。
デフォルトでは200(OK)が設定される。
ログアウト成功時にリダイレクトを行うのが望ましくないRESTful Web Serviceのようなアプリケーションで有用である。
|
DelegatingLogoutSuccessHandler |
RequestMatcher インタフェースの仕組みを利用して、指定されたリクエストのパターンに対応するLogoutSuccessHandler インタフェースの実装クラスに処理を委譲する実装クラス。 |
9.2.2.8.1. デフォルトの動作¶
Spring Securityのデフォルトの動作では、ログインフォームを表示するためのパスにlogout
というクエリパラメータが付与されたURLにリダイレクトする。
例として、ログインフォームを表示するためのパスが/login
の場合は/login?logout
にリダイレクトされる。
9.2.2.9. ログアウト成功時の認証イベントのハンドリング¶
Spring Securityでは、認証イベントのハンドリングと同様に、ログアウト処理の処理結果を他のコンポーネントと連携する仕組みを提供している。
ログアウト成功時の認証イベントの通知は、以下のような仕組みで行われる。
項番 | 説明 |
---|---|
(1)
|
ログアウト処理が成功した後、
LogoutSuccessEventPublishingLogoutHandler は認証イベントクラスのインスタンスを生成し、ApplicationEventPublisher に渡してイベントの通知依頼を行う。 |
以下にSpring Securityが用意しているイベントクラスを説明する。
9.2.2.9.1. ログアウト成功イベント¶
ログアウトが成功した時にSpring Securityが通知するイベントは以下の1つである。
イベントクラス | 説明 |
---|---|
LogoutSuccessEvent |
ログアウトが成功したことを通知するためのイベントクラス。
このイベントをハンドリングすると、クライアントがログアウトし、認証情報が破棄されたことを検知することが可能である。
なお、このイベントをハンドリングした後の後続処理でエラーが発生する可能性がある点に注意されたい。
|
ログアウト成功イベントの通知を受け取って処理を行う方法については、イベントリスナの作成を参照されたい。
9.2.2.10. 認証情報へのアクセス¶
SecurityContextHolderFilter
クラスによってSecurityContextHolder
というクラスに格納され、同一スレッド内であればどこからでもアクセスすることができるようになる。ここでは、認証情報からUserDetails
を取得し、取得したUserDetails
が保持している情報にアクセスする方法を説明する。
9.2.2.10.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 (logger.isInfoEnabled()) {
logger.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
認証に成功したセッションをセッション管理領域に登録するクラス。
9.2.2.10.2. Thymeleafからのアクセス¶
- Thymeleafのテンプレートで認証情報へアクセスする実装例
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!--/* omitted */-->
ようこそ、
<span sec:authentication="principal.account.lastName"></span> <!--/* (1) */-->
さん。
項番 | 説明 |
---|---|
(1)
|
属性値にSpring Security Dialectから提供されている
sec:authentication 属性を使用して、認証情報(Authentication オブジェクト) を取得する。アクセスしたいプロパティへのパスを指定する。
ネストしているオブジェクトへアクセスしたい場合は、プロパティ名を”
. ” でつなげればよい。 |
Tip
#authenticationの紹介
ここでは、sec:authentication
属性を用いて認証情報が保持するユーザー情報を表示する際の実装例を説明したが、Spring Security Dialectから提供されている#authentication
を用いても、ThymeleafのテンプレートHTMLから認証情報にアクセスする事が可能である。
#authentication
は、 変数式${}
式にて使用できるため、条件判定やリテラル置換等sec:authentication
属性より複雑な使い方が可能である。
上記の例は、以下のように記述できる
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><!--/* (1) */--> <!--/* omitted */--> <p th:text="|ようこそ、${#authentication.principal.account.lastName}さん。|"></p><!--/* (2) */-->
項番 説明 (1)sec:authentication
属性を使用する際には<html>
タグにxmlns:sec
属性を定義していたが、#authentication
を使用する際には、xmlns:sec
属性の定義は不要である。 (2)#authentication
にて認証情報よりlastNameを取得し、lastNameの前後にリテラル置換を行っている。
9.2.2.11. 認証処理とSpring MVCの連携¶
9.2.2.11.1. 認証情報へのアクセス¶
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 request-matcher="ant">
<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 request-matcher="ant">
<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 request-matcher="ant">
<sec:form-login default-target-url="/menu" /> <!-- (1) -->
</sec:http>
項番 | 説明 |
---|---|
(1)
|
default-target-url 属性に認証成功時に遷移するデフォルトのパスを指定する。 |
9.2.3.2.2. 遷移先の固定化¶
- spring-security.xmlの定義例
<sec:http request-matcher="ant">
<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 request-matcher="ant">
<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 request-matcher="ant">
<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 request-matcher="ant">
<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 request-matcher="ant">
<!-- 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 request-matcher="ant">
<!-- 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 request-matcher="ant">
<!-- 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
が保持するメッセージには、原因例外のメッセージが設定されるため、画面にそのまま表示するのは適切ではない。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 request-matcher="ant">
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler" />
</sec:http>
systemError
)を付けてログインフォームに遷移させている。systemError
が指定されている場合は、認証例外のメッセージを表示するのではなく、固定のエラーメッセージを表示するようにしている。- ログインフォームの実装例
<span th:if="${param.keySet().contains('error')}" style="color: red;"
th:text="${session[SPRING_SECURITY_LAST_EXCEPTION].message}"></span>
<span th:if="${param.keySet().contains('systemError')}" style="color: red;">
System Error occurred.
</span>
Note
ここでは、ログインフォームに遷移させる場合の実装例を紹介したが、システムエラー画面に遷移させてもよい。
9.2.3.7. 認証時の入力チェック¶
9.2.3.7.1. 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. ログインフォームの修正¶
ログインフォームの作成で作成したログインフォーム(Thymeleaf)に対して、会社識別子を追加する。
<form th:action="@{/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>
項番 | 説明 |
---|---|
(1)
|
会社識別子の入力フィールド名に
companyId を指定する。 |
9.2.3.8.5. 拡張した認証処理の適用¶
ユーザー名、パスワード、会社識別子(独自の認証パラメータ)を使用したDB認証機能をSpring Securityに適用する。
- spring-security.xmlの定義例
<!-- omitted -->
<!-- (1) -->
<sec:http request-matcher="ant"
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
では、ソルトに16バイトの乱数(java.security.SecureRandom
)が使用され、イテレーションカウントはデフォルトでは310,000回に設定されている。
Note
ソルト
ハッシュ化対象のデータに追加する文字列のことである。
ソルトをパスワードに付与することで、実際のパスワードより桁数が長くなるため、レインボークラックなどのパスワード解析を困難にすることができる。なお、ソルトはユーザーごとに異なる値(ランダム値等)を設定することを推奨する。これは、同じソルトを使用していると、ハッシュ値からハッシュ化前の文字列(パスワード)がわかってしまう可能性があるためである。
イテレーション
ハッシュ関数の計算を繰り返し行うことで、保管するパスワードに関する情報を繰り返し暗号化することである。
パスワードの総当たり攻撃への対策として、パスワード解析に必要な時間を延ばすために行う。しかし、イテレーションはシステムの性能に影響を与えるので、システムの性能を考慮してイテレーションカウントを決める必要がある。
Spring Securityのデフォルトでは310,000回イテレーションを行うが、この回数はコンストラクタ引数(
iterations
)で変更することができる。イテレーションカウントが多いほどパスワードの強度は増すが、計算量が多くなるため性能にあたえる影響も大きくなる。
ここではイテレーションカウントの変更方法を紹介する。
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="saltLength" value="16" /> <!-- (3) -->
<constructor-arg name="iterations" value="310000" /> <!-- (4) -->
<constructor-arg name="secretKeyFactoryAlgorithm"
value="#{T(org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm).PBKDF2WithHmacSHA256}" /> <!-- (5) -->
</bean>
</entry>
<!-- omitted -->
</map>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
Pbkdf2PasswordEncoder をカスタマイズするため、使用するコンストラクタをファクトリーメソッドから変更するコンストラクタに指定する引数については以下で解説を行う。
|
(2)
|
secret にはハッシュ化で使用する秘密鍵を指定する。デフォルトは空(”“)である。
|
(3)
|
saltLength にはハッシュ化対象のデータに追加する文字列長を指定する。デフォルトは16である。
|
(3)
|
iterations にはハッシュ化のiterations(反復)回数を指定する。デフォルトでは310,000回が設定されている。
|
(4)
|
secretKeyFactoryAlgorithm にはハッシュ化アルゴリズムを指定する。デフォルトではSHA-256が設定されている。
|
Warning
SecureRandomの使用について
Linux環境でSecureRandom
を使用する場合、処理の遅延やタイムアウトが発生する場合がある。
これは使用する乱数生成器に左右される事象であり、以下のドキュメントに説明がある。
詳しくはSecureRandomのjavadocを参照されたい。
本事象が発生する場合は、以下のいずれかの設定を追加することで回避することができる。
- Javaコマンド実行時に
-Djava.security.egd=file:/dev/urandom
を指定する。 ${JAVA_HOME}/jre/lib/security/java.security
内のsecurerandom.source=/dev/random
をsecurerandom.source=/dev/urandom
に変更する。
9.2.3.10. 非推奨アルゴリズムのPasswordEncoderの利用¶
セキュリティ要件によっては、前述したPasswordEncoder
を実装したクラスでは実現できない場合がある。
特に、既存のアカウント情報で使用しているハッシュ化要件を踏襲する必要がある場合は、前述のPasswordEncoder
では要件を満たせないことがある。
具体的には、既存のハッシュ化要件が以下のようなケースである。
- アルゴリズムがSHA-512である。
- ハッシュ化回数が1000回である。
このようなケースでは、PasswordEncoder
の実装クラスの一つであるMessageDigestPasswordEncoder
を利用することで要件を満たすことができる。
9.2.3.10.1. MessageDigestPasswordEncoderの利用¶
MessageDigestPasswordEncoder
はJavaが提供するjava.security.MessageDigest
クラスを利用してハッシュ化を行う。MessageDigest
クラスはMD-5、SHA-1、SHA-256等のハッシュアルゴリズムを提供している。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”を返却する。
ThymeleafViewResolver によってsrc/main/webapp/WEB-INF/views/login.htmlが出力される。 |
本例のように、単純にview名を返すだけのメソッドが一つだけあるControllerであれば、<mvc:view-controller>
を使用して代用することも可能である。
詳しくは、HTMLを応答するを参照されたい。
9.2.4.2. Remember Me認証の利用¶
Remember Me認証を利用する場合は、<sec:remember-me>
タグを追加する。
- spring-security.xmlの定義例
<sec:http request-matcher="ant">
<!-- 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>) -を参照されたい。
|
ログインフォームには、「Remember Me認証」機能の利用有無を指定するためのフラグ(チェックボックス項目)を用意する。
- ログインフォームのThymeleafでの実装例
<form th:action="@{/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>
項番 | 説明 |
---|---|
(1)
|
「Remember Me認証」機能の利用有無を指定するためのフラグ(チェックボックス項目)を追加し、フィールド名(リクエストパラメータ名)には、
remember-me-parameter のデフォルト値であるremember-me を指定する。チェックボックスの
value 属性には、true を設定する。チェックボックスをチェック状態にしてから認証処理を実行すると、以降のリクエストから「Remember Me認証」機能が適用される。
|