今回は、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のログインし、ドメインなど確認が必要なので、ご承知おきください。
