无 WebSecurityConfigurerAdapter 的 Spring Security

工程 | Eleftheria Stein-Kousathana | 2022年2月21日 | ...

在 Spring Security 5.7.0-M2 中,我们弃用WebSecurityConfigurerAdapter,因为我们鼓励用户转向基于组件的安全配置。

为了协助向这种新的配置风格过渡,我们编制了一份常见用例和今后建议的替代方案列表。

在下面的示例中,我们遵循最佳实践,使用 Spring Security lambda DSL 和方法 HttpSecurity#authorizeHttpRequests 来定义我们的授权规则。如果您不熟悉 lambda DSL,可以在这篇博文中了解它。如果您想了解更多关于我们为什么选择使用 HttpSecurity#authorizeHttpRequests 的信息,可以查看参考文档

配置 HttpSecurity

在 Spring Security 5.4 中,我们引入了通过创建 SecurityFilterChain bean 来配置 HttpSecurity 的能力。

下面是一个使用 WebSecurityConfigurerAdapter 的配置示例,它使用 HTTP Basic 保护所有端点

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    }

}

展望未来,推荐的做法是注册一个 SecurityFilterChain bean

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }

}

配置 WebSecurity

在 Spring Security 5.4 中,我们还引入了 WebSecurityCustomizer

WebSecurityCustomizer 是一个回调接口,可用于自定义 WebSecurity

下面是一个使用 WebSecurityConfigurerAdapter 的配置示例,它忽略与 /ignore1/ignore2 匹配的请求

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/ignore1", "/ignore2");
    }

}

展望未来,推荐的做法是注册一个 WebSecurityCustomizer bean

@Configuration
public class SecurityConfiguration {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
    }

}

警告:如果您正在配置 WebSecurity 以忽略请求,请考虑使用 HttpSecurity#authorizeHttpRequests 通过 permitAll 代替。有关更多详细信息,请参阅 configureJavadoc

LDAP 身份验证

在 Spring Security 5.7 中,我们引入了 EmbeddedLdapServerContextSourceFactoryBeanLdapBindAuthenticationManagerFactoryLdapPasswordComparisonAuthenticationManagerFactory,它们可用于创建嵌入式 LDAP 服务器和执行 LDAP 身份验证的 AuthenticationManager

下面是一个使用 WebSecurityConfigurerAdapter 的配置示例,它创建了一个嵌入式 LDAP 服务器和一个使用绑定身份验证执行 LDAP 身份验证的 AuthenticationManager

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .ldapAuthentication()
            .userDetailsContextMapper(new PersonContextMapper())
            .userDnPatterns("uid={0},ou=people")
            .contextSource()
            .port(0);
    }

}

展望未来,推荐的做法是使用新的 LDAP 类

@Configuration
public class SecurityConfiguration {
    @Bean
    public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
        EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
            EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
        contextSourceFactoryBean.setPort(0);
        return contextSourceFactoryBean;
    }

    @Bean
    AuthenticationManager ldapAuthenticationManager(
            BaseLdapPathContextSource contextSource) {
        LdapBindAuthenticationManagerFactory factory = 
            new LdapBindAuthenticationManagerFactory(contextSource);
        factory.setUserDnPatterns("uid={0},ou=people");
        factory.setUserDetailsContextMapper(new PersonContextMapper());
        return factory.createAuthenticationManager();
    }
}

JDBC 身份验证

下面是一个使用 WebSecurityConfigurerAdapter 和嵌入式 DataSource 的配置示例,该 DataSource 使用默认模式初始化,并且只有一个用户

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        auth.jdbcAuthentication()
            .withDefaultSchema()
            .dataSource(dataSource())
            .withUser(user);
    }
}

推荐的做法是注册一个 JdbcUserDetailsManager bean

@Configuration
public class SecurityConfiguration {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
            .build();
    }

    @Bean
    public UserDetailsManager users(DataSource dataSource) {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
        users.createUser(user);
        return users;
    }
}

注意:在这些示例中,我们出于可读性使用 User.withDefaultPasswordEncoder() 方法。它不适用于生产环境,我们建议您在外部对密码进行哈希处理。一种方法是使用 Spring Boot CLI,如参考文档中所述。

内存身份验证

下面是一个使用 WebSecurityConfigurerAdapter 的配置示例,它使用单个用户配置内存用户存储

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        auth.inMemoryAuthentication()
            .withUser(user);
    }
}

推荐的做法是注册一个 InMemoryUserDetailsManager bean

@Configuration
public class SecurityConfiguration {
    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

注意:在这些示例中,我们出于可读性使用 User.withDefaultPasswordEncoder() 方法。它不适用于生产环境,我们建议您在外部对密码进行哈希处理。一种方法是使用 Spring Boot CLI,如参考文档中所述。

全局 AuthenticationManager

要创建可用于整个应用程序的 AuthenticationManager,您可以简单地将 AuthenticationManager 作为 @Bean 注册。

这种类型的配置如上所示,在LDAP 身份验证示例中。

本地 AuthenticationManager

在 Spring Security 5.6 中,我们引入了方法HttpSecurity#authenticationManager,该方法覆盖特定 SecurityFilterChain 的默认 AuthenticationManager。下面是一个配置示例,它将自定义 AuthenticationManager 设置为默认值

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults())
            .authenticationManager(new CustomAuthenticationManager());
        return http.build();
    }

}

访问本地 AuthenticationManager

可以在自定义 DSL中访问本地 AuthenticationManager。这实际上是 Spring Security 在内部实现 HttpSecurity.authorizeRequests() 等方法的方式。

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        http.addFilter(new CustomFilter(authenticationManager));
    }

    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}

然后,在构建 SecurityFilterChain 时可以应用自定义 DSL

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // ...
    http.apply(customDsl());
    return http.build();
}

参与进来

我们很高兴与您分享这些更新,并期待着通过您的反馈进一步增强 Spring Security!如果您有兴趣贡献,可以在GitHub上找到我们。

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部