Spring Securityの認証結果をJSONにする

# はじめに
Spring Securityで、Form認証の結果をJSONで返却するための方法を調べてみました。

私が検証したバージョンは以下となります。

|            |Version |
|------------|---|
|Java        |1.8 |
|Spring Boot |1.5.10.RELEASE |

# サンプルコード

論よりコード。まずはサンプルコードをご覧ください。

```java:JsonAuthConfigurer.java
package example;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Configuration
public class JsonAuthConfigurer extends WebSecurityConfigurerAdapter
    implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
  private static final MediaType CONTENT_TYPE_JSON = MediaType.APPLICATION_JSON_UTF8;

  @Autowired
  MappingJackson2HttpMessageConverter httpMessageConverter;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() // Form認証を設定
        .successHandler(this) // 認証成功時のハンドラ指定
        .failureHandler(this); // 認証失敗時のハンドラ指定
    http.authorizeRequests().anyRequest().authenticated(); // 認証が要求されるリクエストの設定
  }


  // ------------------------------
  // AuthenticationSuccessHandler
  // ------------------------------

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request,
                                      HttpServletResponse response,
                                      Authentication authentication) throws IOException {
    MyResult result = new MyResult("認証成功"); // JSONにするオブジェクト
    HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
    httpMessageConverter.write(result, CONTENT_TYPE_JSON, outputMessage); // Responseに書き込む
    response.setStatus(HttpStatus.OK.value()); // 200 OK.
  }

  // ------------------------------
  // AuthenticationFailureHandler
  // ------------------------------

  @Override
  public void onAuthenticationFailure(HttpServletRequest request,
                                      HttpServletResponse response,
                                      AuthenticationException exception) throws IOException {
    MyResult result = new MyResult("認証失敗"); // JSONにするオブジェクト
    HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
    httpMessageConverter.write(result, CONTENT_TYPE_JSON, outputMessage); // Responseに書き込む
    response.setStatus(HttpStatus.UNAUTHORIZED.value()); // 401 Unauthorized.
  }


  // ------------------------------

  /** 認証結果 */
  @lombok.Value
  public static class MyResult {
    private final String message;
  }

}
```

# ResponseをJSONにするには
successHandler[^fqcn-s]およびfailureHandler[^fqcn-f]で、Response内容にJSONを書き込めばよいようです。
また、認証失敗の場合はステータスが「401 Unauthorized.」になるようにしています。


# Tips
ResponseをJSONにするための処理があっさり終わってしまったので、いろいろ試してみて気付いたTipsをご紹介します。

## MappingJackson2HttpMessageConverter
Response内容にJSONを書き込む際は、``MappingJackson2HttpMessageConverter``を使うと便利です。
自動的にヘッダに``Content-Type: application/json;charset=UTF-8``もつけてくれます。

```java:MappingJackson2HttpMessageConverterをAutowired
@Autowired
MappingJackson2HttpMessageConverter httpMessageConverter;
```

```java:JSONの書き込み
httpMessageConverter.write(result, CONTENT_TYPE_JSON, outputMessage);
```

ちなみに、自力でResponseに書き込む場合は以下のようになります。[^quote-1]

```java
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  response.getWriter().write(new ObjectMapper().writeValueAsString(new UserAuthenticationResponse(authentication.getName(), 123l)));
  response.setStatus(200);
}
```

## ハンドラのInterface実装
上記サンプルコードのように、各種ハンドラのInterfaceをWebSecurityConfigurerの実装クラスでimplementsするとコードがすっきりします。

```java:すっきりしている箇所の抜粋
@Configuration
public class JsonAuthConfigurer extends WebSecurityConfigurerAdapter
    implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
    // ↑このクラスでハンドラをimplementsしているので...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
        .successHandler(this) // ←ハンドラ指定の引数は、thisでよい
        .failureHandler(this); // ←ハンドラ指定の引数は、thisでよい
  }

  //...
}
```

# おわりに
認証シリーズ第3弾です。過去の記事は以下になります。

- [Spring BootでBasic認証を使用する](https://qiita.com/pale2f/items/10dd41df2da205fd576d)
- [Spring Bootで、ユーザ名とパスワードを指定した認証処理の実装方法](
https://qiita.com/pale2f/items/3fb28e76f969d7c18f06)

Springにも大分慣れてきたような気がします。


[^fqcn-s]: org.springframework.security.web.authentication.AuthenticationFailureHandler
[^fqcn-f]: org.springframework.security.web.authentication.AuthenticationSuccessHandler
[^quote-1]: [Spring boot - return user object after log in - Stack Overflow](https://stackoverflow.com/questions/35241182/spring-boot-return-user-object-after-log-in)から引用