RESTサービスを開発しています。JSONを使用しており、問題が発生した場合は事前定義されたJSONオブジェクトを返す必要があります。デフォルトのSpring応答は次のようになります。
{
"timestamp": 1512578593776,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/swagger-ui.html"
}
このデフォルトのJSONを独自のJSONに置き換えたい(スタックトレースと追加の例外関連情報を含む)。
Springは、デフォルトの動作を上書きする便利な方法を提供します。カスタム例外ハンドラーを使用して@RestControllerAdvice
Beanを定義する必要があります。このような
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {Exception.class})
public ResponseEntity<ExceptionResponse> unknownException(Exception ex) {
ExceptionResponse resp = new ExceptionResponse(ex, level); // my custom response object
return new ResponseEntity<ExceptionResponse>(resp, resp.getStatus());
}
@ExceptionHandler(value = {AuthenticationException.class})
public ResponseEntity<ExceptionResponse> authenticationException(AuthenticationExceptionex) {
// WON'T WORK
}
}
カスタムExceptionResponse
オブジェクトは、特別なメッセージコンバーターを使用してSpringによってJSONに変換されます。
問題は、InsufficientAuthenticationException
のようなセキュリティ例外は@ExceptionHandler
として注釈が付けられたメソッドによってインターセプトできないこと。この種の例外は、Spring MVCディスパッチャーサーブレットが入力され、すべてのMVCハンドラーが初期化される前に発生します。
カスタムフィルターを使用してこの例外をインターセプトし、独自のJSONシリアル化を最初から構築することができます。この場合、SpringMVCインフラストラクチャの残りの部分から完全に独立したコードを取得します。それは良くない。
私が見つけた解決策はうまくいくようですが、それはおかしいようです。
@Configuration
public class CustomSecurityConfiguration extends
WebSecurityConfigurerAdapter {
@Autowired
protected RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Autowired
protected GlobalExceptionHandler exceptionHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated();
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
try {
ResponseEntity<ExceptionResponse> objResponse = exceptionHandler.authenticationException(authException);
Method unknownException = exceptionHandler.getClass().getMethod("authenticationException", AuthenticationException.class);
HandlerMethod handlerMethod = new HandlerMethod(exceptionHandler, unknownException);
MethodParameter returnType = handlerMethod.getReturnValueType(objResponse);
ModelAndViewContainer mvc = new ModelAndViewContainer(); // not really used here.
List<HttpMessageConverter<?>> mconverters = requestMappingHandlerAdapter.getMessageConverters();
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(mconverters);
processor.handleReturnValue(objResponse, returnType, mvc, webRequest);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
};
}
これよりも見栄えの良いSpringシリアル化パイプ(Spring組み込みメッセージコンバーター、MIME形式ネゴシエーションなど)を使用する方法はありますか?
Beanクラスを作成する
_@Component public class AuthenticationExceptionHandler implements AuthenticationEntryPoint, Serializable
_
commence() method
をオーバーライドし、次の例のようにオブジェクトマッパーを使用してjson応答を作成します
_ ObjectMapper mapper = new ObjectMapper();
String responseMsg = mapper.writeValueAsString(responseObject);
response.getWriter().write(responseMsg);
_
および_@Autowire AuthenticationExceptionHandler
_ SecurityConfiguration
クラスおよびconfigure(HttpSecurity http) method
に以下の行を追加します
_ http.exceptionHandling()
.authenticationEntryPoint(authenticationExceptionHandler)
_
このようにして、カスタムjson応答401/403を送信できるはずです。上記と同じように、AccessDeniedHandler
を使用できます。これが問題の解決に役立ったかどうかをお知らせください。
次の設定でうまくいくと思いますので、ぜひお試しください。
@Autowired
private HandlerExceptionResolver handlerExceptionResolver;
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
try {
handlerExceptionResolver.resolveException(request, response, null, authException);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
};
}
Springは、 AccessDeniedHandler
を介して例外処理をすぐにサポートします。これは、以下の手順に従って、AccessDeniedException
の場合にカスタムJSON応答を実現することで利用できます。すなわちHTTP 403
以下のようなカスタムハンドラーを実装します
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc)
throws IOException, ServletException {
System.out.println("Access denied .. ");
// do something
response.sendRedirect("/deny");
}
}
次に、configでこのハンドラーのBeanを作成し、Springセキュリティ例外ハンドラーに提供します(重要な注意-/deny
を除外してください認証からそれ以外のリクエストは無限にエラーを解決し続けます)
@Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
// some other configuration
.antMatchers("/deny").permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
次に、コントローラークラスで、/deny
に対応するハンドラーを記述し、SomeException
(またはその他のException
に適した)の新しいインスタンスをスローします。 @RestControllerAdvice
それぞれのハンドラーでインターセプトされます。
@GetMapping("/deny")
public void accessDenied(){
throw new SomeException("User is not authorized");
}
さらに情報が必要な場合はコメントでお知らせください。