概要
認証エラー時に表示されるエラーメッセージをカスタマイズする方法についてまとめた。
前提
以下の記事の続きとなる。
概要 自作ログイン画面を利用した、簡易的なログイン認証機能の利用方法についてまとめた。 前提 以下の記事の続きとなる。
メッセージ仕様
エラーメッセージは以下の方針でカスタマイズする。
・エラーに応じて自作したメッセージで上書きする。
補足として、Spring Securityが提供するデフォルトのメッセージを一部紹介する。
※spring-security-core-5.8.5.jarの場合
/org/springframework/security/messages_ja.properties
AbstractUserDetailsAuthenticationProvider.badCredentials=ユーザ名かパスワードが正しくありません
AbstractUserDetailsAuthenticationProvider.credentialsExpired=ユーザ認証情報の有効期限が切れています
AbstractUserDetailsAuthenticationProvider.disabled=無効なユーザです
AbstractUserDetailsAuthenticationProvider.expired=ユーザアカウントの有効期限が切れています
AbstractUserDetailsAuthenticationProvider.locked=ユーザアカウントがロックされています
AbstractUserDetailsAuthenticationProvider.onlySupports=UsernamePasswordAuthenticationTokenのみサポートされています
実装
エラーメッセージを自作プロパティに定義し、Spring Securityフレームワークに適用させる。
メッセージ定義
前回の記事にて自作したログイン画面では、画面項目を「ログインID」とした。
そのため、画面項目に合わせたエラーメッセージを定義する。
/src/main/resources/messages_ja.properties
######################################
# Spring Security提供のメッセージをカスタマイズ
######################################
AbstractUserDetailsAuthenticationProvider.badCredentials=ログインIDまたはパスワードが間違っています
メッセージソース定義
自作プロパティからメッセージ内容を取得するため、メッセージソースを使用する。
DIコンテナ(ルートアプリケーションコンテキスト)にBeanを登録して、自作プロパティとSpring Securityが提供しているメッセージプロパティを参照させる。
概要 Springのメッセージ管理を行うMessageSourceを使用して外部ファイルからメッセージを取得する方法についてまとめた。 MessageSourceはプロパティファイルに定義したメッセージを取得する機能を提供する。 […]
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- コンポーネントスキャン -->
<context:component-scan base-package="com.example.prototype.biz" />
<!-- ResourceBundleMessageSourceを使用する -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages</value> <!-- 自作 -->
<value>org/springframework/security/messages</value> <!-- Spring SecurityのJAR内 -->
</list>
</property>
</bean>
</beans>
<value>org/springframework/security/messages</value> <!– Spring SecurityのJAR内 –>
メッセージソースは、basenamesに指定されたプロパティファイルを上から順に参照する。
そのため、上記のように自作プロパティを先に参照させることで、
両者に同じプロパティキーが存在する場合でも、自作のメッセージが優先して参照される。
これにより、Spring Securityが内部で使用するメッセージを任意の文言で上書きすることができる。
メッセージソースの差し替え
DaoAuthenticationProviderにてデフォルトのメッセージソースを利用しているため、これを今回定義したメッセージソースに差し替える。

SecurityConfig.java
package com.example.prototype.biz.security.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public DaoAuthenticationProvider authenticationProvider(MessageSource messageSource) {
var provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
// メッセージソースの設定
provider.setMessageSource(messageSource);
return provider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF機能を無効
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// 認証不要のURL
.requestMatchers(
new AntPathRequestMatcher("/authentication")
).permitAll()
// その他のURLは認証が必要
.anyRequest().authenticated())
.formLogin(form -> form
// ログイン画面URL
.loginPage("/authentication")
// UsernamePasswordAuthenticationFilterが認証処理を行うURL
.loginProcessingUrl("/authentication/process")
// フォームの name="loginId" を認識
.usernameParameter("loginId")
// フォームの name="pass" を認識
.passwordParameter("pass")
.defaultSuccessUrl("/", true));
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails disabledUser = User.withUsername("user")
.password("{noop}password")
.roles("USER")
.disabled(true) // アカウント無効状態
.build();
return new InMemoryUserDetailsManager(disabledUser);
}
}
public DaoAuthenticationProvider authenticationProvider(MessageSource messageSource) {
DaoAuthenticationProviderをカスタマイズするため、Bean定義する。
メッセージソースを差し替えることで、Spring Securityフレームワークへのメッセージ適用が完了する。(デフォルトのメッセージプロパティと自作プロパティを参照するようになる)
エラーメッセージ表示領域定義
ログイン画面にエラーメッセージ表示領域を追加する。
login.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ログイン画面</title>
</head>
<body>
<h2>ログイン</h2>
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
<p style="color: red;">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
</p>
</c:if>
<form action="${pageContext.request.contextPath}/authentication/process" method="post">
<label for="loginId">ログインID:</label>
<input type="text" id="loginId" name="loginId" autofocus />
<br />
<label for="password">パスワード:</label>
<input type="password" id="pass" name="pass" />
<br />
<input type="submit" value="ログイン" />
</form>
<br>
</body>
</html>
「SPRING_SECURITY_LAST_EXCEPTION」は、Spring Securityが認証失敗時にセッションスコープへエラー内容を保持するためのキーとなる。
ここからエラーメッセージを取得することができる。
動作確認
自作したエラーメッセージと、Spring Securityが提供するエラーメッセージが有効になったことを確認する。


まとめ
☑ メッセージソースを利用することで、Spring Securityが内部で使用するメッセージを自作の内容で上書きできる
☑ 定義したメッセージソースを有効にするには、DaoAuthenticationProviderが保持するメッセージソースを差し替える必要がある
☑ 認証失敗時には、SPRING_SECURITY_LAST_EXCEPTIONをキーにエラー情報を取得できる

