質問するログイン新規登録
Spring Security

Spring Securityは、Springのサブプロジェクトの一つでWebアプリケーションに必要な機能を追加します。正規ユーザーであるかを確認するための「認証機能」と、ユーザーのアクセスを制御する「認可機能」を簡単に追加することが可能です。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

1回答

460閲覧

Spring Boot (Spring Security)で、未保護エンドポイントからResponseStatusExceptionをスローしても、指定したステータスコードで返却されない

_naka2shi_

総合スコア1

Spring Security

Spring Securityは、Springのサブプロジェクトの一つでWebアプリケーションに必要な機能を追加します。正規ユーザーであるかを確認するための「認証機能」と、ユーザーのアクセスを制御する「認可機能」を簡単に追加することが可能です。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

0クリップ

投稿2024/01/28 09:27

0

0

実現したいこと

Spring Boot (Spring Security)で、以下の機能を実現するREST APIを作成しようとしています。

  1. 未保護エンドポイント /unprotect にアクセスすると、HTTPStatus 200が返ってくる
  2. 保護エンドポイント /protect にアクセスすると、Basic認証が要求される
    1. Basic認証に成功すると、HTTPStatus 200が返ってくる
    2. Basic認証に失敗すると、HTTPStatus 500と個有のレスポンスボディが返ってくる
  3. /unprotect、/protectいずれも、業務ロジックに応じて、特定のHTTPStatusを返却する

/unprotect のコントローラーから、ResponseStatusExceptionをスローしても、指定したステータスコードで返却されない問題が発生しており、こちらの問題を解消したいです。

前提

未保護エンドポイント、保護エンドポイントはURLで切り替えています。
Basic認証に失敗した場合に、個有のレスポンスボディを返却する必要があるため、authenticationEntryPointを指定しています。
実装は以下の通りです。

java

1@Configuration 2@EnableWebSecurity 3public class SecurityConfig { 4 5 @Bean 6 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 7 http.csrf(csrf -> csrf.disable()) 8 .authorizeHttpRequests(a -> a 9 .requestMatchers("/unprotect").permitAll() 10 .anyRequest().authenticated()) 11 .formLogin(f -> f.disable()) 12 .httpBasic(Customizer.withDefaults()) 13 .exceptionHandling((e) -> e.authenticationEntryPoint(new MyAuthenticationEntryPoint())); 14 15 return http.build(); 16 } 17}

java

1@Slf4j 2public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { 3 4 @Override 5 public void commence(HttpServletRequest request, HttpServletResponse response, 6 AuthenticationException authException) throws IOException, ServletException { 7 8 log.info("call MyAuthenticationEntryPoint commence"); 9 10 authException.printStackTrace(); 11 12 response.getWriter().write("MyAuthenticationEntryPoint response"); 13 response.setStatus(500); 14 } 15}

コントローラーでは、クエリパラメーター "p" をつけてリクエストするとHttpStatus 200、なければHttpStatus 400を返すようにしています。
実装は以下の通りです。

java

1@RestController 2@Slf4j 3public class MyController { 4 5 @GetMapping("/protect") 6 public String protect(@RequestParam(value = "p", required = false) String p) { 7 log.info("call protect"); 8 return hello(p); 9 } 10 11 @GetMapping("/unprotect") 12 public String unprotect(@RequestParam(value = "p", required = false) String p) { 13 log.info("call unprotect"); 14 return hello(p); 15 } 16 17 private String hello(String e) { 18 if (e == null || "".equals(e)) { 19 log.info("throw exception"); 20 throw new ResponseStatusException(HttpStatus.BAD_REQUEST); 21 } else { 22 log.info("return response"); 23 return "Hello, " + e; 24 } 25 } 26 27}

認証を行う部分は以下のように実装しています。

java

1@Component 2public class MyAuthenticationProvider implements AuthenticationProvider { 3 4 @Override 5 public Authentication authenticate(final Authentication authentication) { 6 7 final Object principal = authentication.getPrincipal(); 8 if (!"myname".equals(authentication.getName())) { 9 throw new BadCredentialsException("name is not myname"); 10 } 11 12 final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, 13 authentication.getCredentials(), Collections.emptyList()); 14 result.setDetails(authentication.getDetails()); 15 16 return result; 17 } 18 19 @Override 20 public boolean supports(final Class<?> authentication) { 21 return true; 22 } 23}

発生している問題・エラーメッセージ

未保護エンポイント /unprotectに "p"パラメーターなしでアクセスすると、求めているHTTPStatus 400ではなく、MyAuthenticationEntryPointで返却しているレスポンスが返ってきます。

$ curl -D - localhost:8080/unprotect HTTP/1.1 500 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 0 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Set-Cookie: JSESSIONID=5DF3BF5182D9CCAC62D7B8BA3BF36E1F; Path=/; HttpOnly Content-Length: 35 Date: Sun, 28 Jan 2024 08:37:20 GMT Connection: close MyAuthenticationEntryPoint response

このとき、Spring Boot側には、以下のログが出力されています。
コントローラーのメソッドを処理したあとに、MyAuthenticationEntryPointが呼ばれています。

2024-01-28T17:41:36.910+09:00 INFO 59621 --- [nio-8080-exec-3] com.example.MyController : call unprotect 2024-01-28T17:41:36.910+09:00 INFO 59621 --- [nio-8080-exec-3] com.example.MyController : throw exception 2024-01-28T17:41:36.910+09:00 WARN 59621 --- [nio-8080-exec-3] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.server.ResponseStatusException: 400 BAD_REQUEST] 2024-01-28T17:41:36.911+09:00 INFO 59621 --- [nio-8080-exec-3] com.example.MyAuthenticationEntryPoint : call MyAuthenticationEntryPoint commence org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource at org.springframework.security.web.access.ExceptionTranslationFilter.handleAccessDeniedException(ExceptionTranslationFilter.java:199) at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:178) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:147) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195) at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:225) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) (省略)

例外をスローしない状態なら、想定しているHttpStatus 200が返ってきます。
そのため、/unprotect はSpring Securityで保護されていないという認識です。

$ curl -D - localhost:8080/unprotect?p=unprotect HTTP/1.1 200 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 0 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Content-Type: text/plain;charset=UTF-8 Content-Length: 16 Date: Sun, 28 Jan 2024 08:46:35 GMT Hello, unprotect

/unprotect のコントローラーにて、ResponseStatusExceptionをスローした際に、指定したステータスコードで返却するには、どうしたらよいのでしょうか?

補足情報(FW/ツールのバージョンなど)

  • Spring Boot 3.2.2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

Spring Securityのissueのページに同事象の報告がありました。
https://github.com/spring-projects/spring-security/issues/14340

こちらの回答を参考に、requestMatchers に "/error" を追加したところ、コントローラーがスローしたResponseStatusExceptionのHTTPStatusコードで返却されることを確認しました。

投稿2024/01/31 05:51

_naka2shi_

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問