はじめに
ログインページを自作して、渡したクエリパラメータの値によって表示を一部いじりたい、というユースケースで問題が発生しました。
問題の外観としては、/login?sample=hogeにアクセスしたとき、/loginにリダイレクトされてしまう、というものです。
結論から
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/css/**", "/error/**").permitAll()
// ★ sampleパラメータがついたURLを限定的に許可するRequestMatcherの追加
.requestMatchers(new CustomLoginPageRequestMatcher()).permitAll()
.anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll());
return http.build();
}
// ★ sampleパラメータがついたURLを許可するMatcherの定義
private static final class CustomLoginPageRequestMatcher implements RequestMatcher {
private static final Set<String> ALLOWED_PARAMS = Set.of("sample", "error", "logout");
private final RequestMatcher pathDelegater = request -> "/login".equals(request.getServletPath())
&& "GET".equalsIgnoreCase(request.getMethod());
@Override
public boolean matches(HttpServletRequest request) {
// ★ /loginに対してGETでアクセスしたとき以外はマッチしない
if (!pathDelegater.matches(request)) {
return false;
}
// ★ sample, error, logout以外がついていたらfalseを返す
return request.getParameterMap().keySet().stream()
.allMatch(ALLOWED_PARAMS::contains);
}
}
}
/loginに対して、認証なしでどんなクエリパラメータを受け入れても良いなら下記でも可
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/css/**", "/error/**", "/login").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll());
return http.build();
}
}
原因
http.formLogin(form -> form.loginPage("/login").permitAll());
formLoginによるログインページのアクセス制御が、以下のURLしかアクセス許可されないことによって発生します。
/login/login?error/login?logout
/login?sample=hogeは上記のURLにマッチしないため、アクセスには認証を必要とします。ログインページにアクセスしているという状況なので、当然認証されていないためアクセス拒否され、クエリパラメータのついていない/loginにリダイレクトされます。
ちなみにSpring Securityのドキュメントには、このような形でアクセス許可されるURLについて記載があります。
この値を更新すると、他の多くのデフォルト値にも影響します。例: formLogin() のみが指定された場合のデフォルト値は次のとおりです。
- /login GET - the login form
- /login POST - process the credentials and if valid authenticate the user
- /login?error GET - redirect here for failed authentication attempts
- /login?logout GET - redirect here after successfully logging out
"/authenticate" がこのメソッドに渡された場合、以下に示すようにデフォルトが更新されます。
- /authenticate GET - the login form
- /authenticate POST - process the credentials and if valid authenticate the user
- /authenticate?error GET - redirect here for failed authentication attempts
- /authenticate?logout GET - redirect here after successfully logging out
余談ですが、TRACEログを眺めると、/login?sample=hogeがアクセス拒否され、/loginへリダイレクトされる様子が観察できます。
2025-08-11T01:17:36.393+09:00 DEBUG 1126860 --- [nio-9000-exec-4] o.s.security.web.FilterChainProxy : Securing GET /login?sample=hoge
...
2025-08-11T01:17:36.396+09:00 TRACE 1126860 --- [nio-9000-exec-4] o.s.s.w.a.ExceptionTranslationFilter : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=69BADCDB51A0533BE717B1191BCFAF2B], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied
org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:99) ~[spring-security-web-6.5.2.jar:6.5.2]
at
...
2025-08-11T01:17:36.399+09:00 DEBUG 1126860 --- [nio-9000-exec-4] o.s.s.w.s.HttpSessionRequestCache : Saved request http://127.0.0.1:9000/login?sample=hoge&continue to session
2025-08-11T01:17:36.399+09:00 DEBUG 1126860 --- [nio-9000-exec-4] o.s.s.web.DefaultRedirectStrategy : Redirecting to http://127.0.0.1:9000/login
おわりに
今回は、Spring SecurityのHttpSecurity.formLoginを利用したときに遭遇した問題について取り上げました。
同じような問題に遭遇した方の参考になれば幸いです。