1
5

More than 5 years have passed since last update.

Spring SecurityのBasic認証をちょっぴりカスタマイズする

Last updated at Posted at 2019-05-25

はじめに

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で差し替える→本来の意図と違う気がして不採用
  • addFilterBeforeaddFilterAfterで独自フィルターを追加する→大掛かりになりすぎるため不採用
1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5