Auth0+Spring SecurityでAccess Tokenを認証する。

今回は、Auth0のアクセストークをSpring Securityで認証する方法についてみていきたいと思います。

以下の技術を使います。

  • Maven
  • Spring boot
  • Spring Security
  • Auth0

Mavenに依存性追加

Oauth2のライブラリを追加します。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Spring Security設定

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  JwtAuthProcessor jwtAuthProcessor;

  @Autowired
  RestAuthenticationFailureEntryPoint restAuthenticationEntryPoint;

  @Override
  public void configure(HttpSecurity http) throws Exception {

    http.csrf().disable().rememberMe();
    http.logout().disable();
    http.cors(Customizer.withDefaults());

    // filter
    http.addFilterBefore(new JwtAuthFilter(jwtAuthProcessor),
        UsernamePasswordAuthenticationFilter.class);

    http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);

    // session
   http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    // public
    http.authorizeRequests()
        .antMatchers("/api/users/**").permitAll();

    // private
    http.authorizeRequests()
        .antMatchers("/api/private").authenticated();
  }


}
http.addFilterBefore(new JwtAuthFilter(jwtAuthProcessor),
        UsernamePasswordAuthenticationFilter.class);

UsernamePasswordAuthenticationFilterを使い、JWT Access Tokenを認証しようと思います。

UsernamePasswordAuthenticationFilterはForm認証でよく使われ、その用途で作られましたが、カスタマイズでトークン認証も可能です。

JwtAuthFilter

public class JwtAuthFilter extends GenericFilter {

  private JwtAuthProcessor jwtAuthProcessor;

  public JwtAuthFilter(JwtAuthProcessor jwtAuthProcessor) {
    this.jwtAuthProcessor = jwtAuthProcessor;
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain filterChain) throws IOException, ServletException {

    try {
      Authentication authentication = this.jwtAuthProcessor.authenticate(
          (HttpServletRequest) request);
      if (authentication != null) {
        SecurityContextHolder.getContext().setAuthentication(authentication);
      } else {
        SecurityContextHolder.clearContext();
      }
    } catch (Exception e) {
      ((HttpServletResponse) response).sendError(HttpStatus.SC_UNAUTHORIZED);
      response.getWriter().print(e);
      response.setContentType("application/json");
      SecurityContextHolder.clearContext();
      e.printStackTrace();
      return;
    }

    filterChain.doFilter(request, response);
  }
}

このfilterは認証結果を「SecurityContextHolder」セットしたり、失敗時はクリアするfilterです。

private JwtAuthProcessor jwtAuthProcessor; 

実際に、トークン認証を行うクラスは上記のクラスです。

JwtAuthProcessor

@Component
public class JwtAuthProcessor {

  private static final String HTTP_HEADER = "Authorization";

  @Autowired
  private UserRepository userRepository;

  public Authentication authenticate(HttpServletRequest request) throws IOException {
    String bearerToken = request.getHeader(HTTP_HEADER);
    if (bearerToken != null) {
      String accessToken = this.getBearerToken(bearerToken);
      if (accessToken == null) {
        return null;
      }

      String json = getUserInfo(bearerToken);
      ObjectMapper objectMapper = new ObjectMapper();
      Map<String, String> map = objectMapper.readValue(json, HashMap.class);

      String email = map.get("email");
      Optional<User> savedUser = userRepository.findByEmail(email);
      if (savedUser.isPresent()) {
        String username = savedUser.get().getUsername();

        List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken(
                String.valueOf(username + "::" + email),
                null, grantedAuthorities);

        SecurityUserDetails userDetails = new SecurityUserDetails(username, email);
        token.setDetails(userDetails);
        return token;
      }

    }
    return null;
  }

  private String getUserInfo(String bearerToken) throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet("https://[domain from setting]/userInfo");

    httpGet.addHeader("Content-type", "application/json");
    httpGet.addHeader("Authorization", bearerToken);

    CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
    String json = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");

    httpClient.close();

    return json;

  }

  private String getBearerToken(String token) {
    return token.startsWith("Bearer ") ? token.substring("Bearer ".length()) : null;
  }
}
  • リクエストから「bearerToken」を取得し、存在チェックを行います。
  • getUserInfo(String bearerToken):Auth0 APIを呼び出し、ユーザー情報を取得します。
    • new HttpGet(“https://%5Bdomain from setting]/userInfo”):Auth0 管理サイトにあるドメインを設定する必要があります。
  • userRepository.findByEmail(email):Auth0のユーザー情報からメールアドレスを取得し、DB検索を行います。
  • UsernamePasswordAuthenticationToken:DBにユーザー情報があるのであれば、認証情報を保存します。
  • token.setDetails(userDetails):ユーザー詳細情報を作成し、「UsernamePasswordAuthenticationToken」にセットします。

終わりに

Spring Securityの細かい設定の話はしていないですが、それはSpring Security記事を書くときに書こうと思っています。

API利用にはAuth0のログインし、ドメインなど確認が必要なので、ご承知おきください。

コメントを残す