【Spring Security】認証エラー時に独自のJSONを返す方法
Contents
やりたいこと
Spring MVCで作成したRest APIに対するリクエストが認証エラーとなった際に独自のJSONをレスポンスとして返すようにします。
認証はリクエストのAuthorizationヘッダ中のトークンを使用しリクエストごとに認証を行います。この認証方法は別記事にしています。
実装方法の概要
実装のポイントは以下の3点です。
- エラーレスポンスを表すクラスを作成する。
- AuthenticationEntryPointインターフェイスを実装したクラスをBean登録する。
- Spring SecurityのauthenticationEntryPointに2.で作成したBeanを指定する。
エラーレスポンスクラスの作成
エラーレスポンスとして返したいプロパティを持つクラスを定義します。ここではエラーコードとエラー内容を持つ以下のクラスを定義します。ゲッター、セッター、コンストラクタはLombokで生成します。
// importは省略
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
private String errorCode;
private String message;
}
AuthenticationEntryPointインターフェイスの実装クラスのBean定義
AuthenticationEntryPointインターフェイスを実装したクラスのBean定義を行います。
AuthenticationEntryPointのcommenceメソッド中で引数のresponseに対してエラーコード、Content type、レスポンスボディを設定します。レスポンスボディが作成したエラーレスポンスクラスのJSONとするためにエラーレスポンスクラスをObjectMapperを使ってJSON文字列へ変換しています。
AuthenticationEntryPointは抽象メソッドが1つなのでBean定義はラムダ式を使って以下のように書くことができます。
@Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
// エラーレスポンスクラスをJSONへ変換するために使う。
var objectMapper = new ObjectMapper();
return (request, response, authException) -> {
// ステータスコードを401とする。
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// Content typeをapplication/jsonとする。
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// レスポンスボディをエラーレスポンスクラスのJSONとする
response.getOutputStream().println(objectMapper.writeValueAsString(new ErrorResponse("unauthorized", "Invalid access token")));
};
}
AuthenticationEntryPointへの登録
作成したBeanをSpring SecurityのAuthenticationEntryPointへ登録します。
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity()
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.addFilter(preAuthenticatedProcessingFilter())
.exceptionHandling()
.authenticationEntryPoint(unauthorizedEntryPoint()) // ここで登録
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.cors();
}
動作確認
ローカルで起動しcurlでリクエストを送って動作確認を行います。Authorizationヘッダにエラーとなる値を設定します。以下のように設定した値となっていることがわかります。
$ curl -H 'Authorization:key22' -i http://localhost:8080/items/1\?name\=111
HTTP/1.1 401 ←ステータスコードが401となっている
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json ←Contet typeがapplication/jsonになっている
Content-Length: 63
Date: Fri, 15 Jan 2021 13:25:47 GMT
{"errorCode":"unauthorized","message":"Invalid access token"} ←作成したエラーレスポンスクラスのJSONとなっている。