Spring Security 7にアップグレードしたらxmlでの設定が使えなくなった

XMLファイルでSpring Securityを設定するSecurityConfigは非推奨になっていたが、今回完全に削除されたのかクラスファイルが見つからなくなった。

流石にJavaファイルで設定する方式に変更した。

元のxmlファイルは下記の通り。
※ExtendedAuthenticationSuccessHandler、ExtendedAuthenticationFailureHandlerはログイン成功失敗時に独自処理を行うためのラッパークラス。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">

    <!-- Resources not processed by spring security filters -->
    <http pattern="/images/**" security="none" />
    <http pattern="/scripts/**" security="none" />
    <http pattern="/styles/**" security="none" />

    <http>
        <intercept-url pattern="/error" access="permitAll" />
        <intercept-url pattern="/login*/**" access="permitAll" />
        <intercept-url pattern="/signup*" access="permitAll" />
        <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
        <intercept-url pattern="/**" access="isAuthenticated()" />

        <form-login login-page="/login" authentication-success-handler-ref="authenticationSuccessHandler" authentication-failure-handler-ref="authenticationFailureHandler" />
        <remember-me user-service-ref="userDetails" key="aaa" />
        <logout logout-url="/logout" logout-success-url="/login" invalidate-session="true" delete-cookies="aaa" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userDetails">
            <password-encoder ref="passwordEncoder" />
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="authenticationSuccessHandler" class="common.webapp.filter.ExtendedAuthenticationSuccessHandler">
        <beans:constructor-arg value="/top" />
    </beans:bean>

    <beans:bean id="authenticationFailureHandler" class="common.webapp.filter.ExtendedAuthenticationFailureHandler">
        <beans:property name="exceptionMappings">
            <beans:props>
                <beans:prop key="org.springframework.security.authentication.DisabledException">/login/accountDisabled</beans:prop>
                <beans:prop key="org.springframework.security.authentication.LockedException">/login/accountLocked</beans:prop>
                <beans:prop key="org.springframework.security.authentication.AccountExpiredException">/login/accountExpired</beans:prop>
                <beans:prop key="org.springframework.security.authentication.CredentialsExpiredException">/login/credentialsExpired</beans:prop>
                <beans:prop key="org.springframework.security.authentication.BadCredentialsException">/login/badCredentials</beans:prop>
            </beans:props>
        </beans:property>
    </beans:bean>

    <beans:bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />

</beans:beans>

これに対して、Javaファイルは下記の通り。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final UserDetailsService userDetailsService;

    public SecurityConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/images/**", "/scripts/**", "/styles/**",
                                 "/error", "/login*/**", 
                                 "/signup*").permitAll()
                .requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .permitAll()
            )
            .rememberMe(remember -> remember
                .userDetailsService(userDetailsService)
                .key("aaa")
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID", "aaa")
            );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public PasswordEncoder passwordTokenEncoder() {
        return new BCryptPasswordEncoder();
    }

    AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new ExtendedAuthenticationSuccessHandler("/top");
    }

    AuthenticationFailureHandler authenticationFailureHandler() {
        // 例外マッピングの設定
        Properties mappings = new Properties();
        mappings.put(DisabledException.class.getName(), "/login/accountDisabled");
        mappings.put(LockedException.class.getName(), "/login/accountLocked");
        mappings.put(AccountExpiredException.class.getName(), "/login/accountExpired");
        mappings.put(CredentialsExpiredException.class.getName(), "/login/credentialsExpired");
        mappings.put(BadCredentialsException.class.getName(), "/login/badCredentials");

        ExtendedAuthenticationFailureHandler handler = new ExtendedAuthenticationFailureHandler();
        handler.setExceptionMappings(mappings);
        return handler;
    }
}

StandardPasswordEncoderは非推奨

Deprecated.
Digest based password encoding is not considered secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder. Even better use DelegatingPasswordEncoder which supports password upgrades. There are no plans to remove this support. It is deprecated to indicate that this is a legacy implementation and using it is considered insecure.

DelegatingPasswordEncoderを使用する場合は、下記の通り。

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        // デフォルトはbcrypt
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

又は、

<bean id="passwordEncoder" class="org.springframework.security.crypto.factory.PasswordEncoderFactories" factory-method="createDelegatingPasswordEncoder" />