はじめに
Spring BootでWeb APIのアプリケーション構築するにあたり、APIの認証としてBasic認証を採用した。
Spring Securityを利用し、諸先輩方の有益な情報を参考にすることで、いとも簡単にBasic認証を実現できたのだが、認証処理をほんの少しだけカスタマイズしたくなり、いろいろ試してみた。
環境
Java: 1.8.0
Spring Framework: 5.1
Spring Boot: 2.1
Spring Security: 5.1
Lombok: 1.18
設定ファイルに登録した認証情報でBasic認証する
application.propertiesに設定したユーザーID・パスワードでBasic認証するSpring Securityの設定は、Spring BootのConfigurationPropertiesを使ってこんな感じで実装。
CustomSecurityConfigurer.java
@Configuration
@EnableWebSecurity
public class CustomSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthInfo authInfo; // application.propertiesで設定した認証情報
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// application.propertiesで設定したユーザID・パスワードをinMemoryにもつ
// application.propertiesに設定するパスワードはBCryptでハッシュ化しておく
for (UserCredential credential: authInfo.getCredentials()) {
authenticationManagerBuilder.inMemoryAuthentication().withUser(credential.getUserName())
.password(credential.getPassword()).roles("USER");
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().realmName("Custom Realm"); // Basic 認証を有効にする
http.authorizeRequests().anyRequest().authenticated(); // 全てのリクエスト対し、認証を行う
http.csrf().disable(); // あえてCSRF対策は無効に設定
// アクセスの都度、認証を行うこととし、セッション管理は行わない
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // パスワードはBCryptでハッシュ化する
}
}
CustomAuthInfo.java
@Getter
@Setter
@Component
@ConfigurationProperties(prefix="custom-auth")
public class CustomAuthInfo {
private List<UserCredential> credentials;
@Getter
@Setter
public static class UserCredential {
private String userName;
private String password;
}
}
application.properties
custom-auth.credentials[0].user-name=user1
custom-auth.credentials[0].password=[BCryptでハッシュ化したパスワード]
custom-auth.credentials[1].user-name=user2
custom-auth.credentials[1].password=[BCryptでハッシュ化したパスワード]
custom-auth.credentials[2].user-name=user3
custom-auth.credentials[2].password=[BCryptでハッシュ化したパスワード]
Spring Security の Basic認証フィルターをカスタマイズしてみる
認証成功時と失敗時に追加したい処理があったので、Spring SecurityのPost Processingとやらを使って、BasicAuthenticationFilterをちょっと強引にカスタマイズしてみた。
CustomSecurityConfigurer.java
//[前略]
private static final String CUSTOM_REALM = "Custom realm";
@Override
protected void configure(HttpSecurity http) throws Exception {
// Basic 認証を有効にする
// 認証フィルターは認証成功、失敗時の追加処理を実装したカスタムフィルターを使用するため、標準フィルターをカスタムフィルターに差し替える
http.httpBasic().realmName(CUSTOM_REALM)
.withObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {
@SuppressWarnings("unchecked")
@Override
public BasicAuthenticationFilter postProcess(BasicAuthenticationFilter filter) {
return new CustomBasicAuthenticationFilter(
http.getSharedObject(AuthenticationManager.class),
authenticationEntryPoint());
}
private BasicAuthenticationEntryPoint authenticationEntryPoint() {
BasicAuthenticationEntryPoint authenticationEntryPoint =
new BasicAuthenticationEntryPoint();
authenticationEntryPoint.setRealmName(CUSTOM_REALM);
return authenticationEntryPoint;
}
});
//[後略]
CustomBasicAuthenticationFilter.java
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager,
AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader("Authorization");
// Basic認証のリクエストヘッダーが無ければ、認証失敗時の処理を実行
if (header == null || !header.toLowerCase().startsWith("basic ")) {
onUnsuccessfulAuthentication(request, response, null);
}
super.doFilterInternal(request, response, chain);
}
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
// 認証成功時にやりたいことをゴニョゴニョする
}
@Override
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
// 認証失敗時にやりたいことをゴニョゴニョする
}
}
その他に試してみたこと
- RememberMeServicesを利用する→目的外使用になっていたため不採用
- addFilterAtで差し替える→addFilterAtは差し替えではなく併立だったので不採用
- Spring FrameworkのOncePerRequestFilterを継承していることに目をつけて、BeanNameを上書きして、addFilterBeforeで差し替える→本来の意図と違う気がして不採用
- addFilterBefore、addFilterAfterで独自フィルターを追加する→大掛かりになりすぎるため不採用