使用 Java 配置和 Spring Security 3.2 的安全方法注释

Posted

技术标签:

【中文标题】使用 Java 配置和 Spring Security 3.2 的安全方法注释【英文标题】:Security Method Annotations with Java Configuration and Spring Security 3.2 【发布时间】:2013-10-21 15:00:56 【问题描述】:

我在使用由@EnableGlobalMethodSecurity 控制的方法级别注释设置我的应用程序时遇到一些问题我正在使用 Servlet 3.0 样式初始化

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer 

    public SecurityWebApplicationInitializer() 
        super(MultiSecurityConfig.class);
    

我尝试了 2 种不同的方式来初始化 AuthenticationManager,它们都有自己的问题。请注意,使用@EnableGlobalMethodSecurity 会导致服务器成功启动,并且所有表单安全性都按预期执行。当我在控制器上添加 @EnableGlobalMethodSecurity@PreAuthorize("hasRole('ROLE_USER')") 注释时,我的问题就出现了。

我正在尝试独立设置基于表单和基于 api 的安全性。基于方法的注解只需要对 api 安全起作用。

一种配置如下。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig 

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter 
        protected void configure(HttpSecurity http) throws Exception 
            http.antMatcher("/api/**").httpBasic();
        

        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception 
            auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
        
    

    @Configuration
    public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 
        public void configure(WebSecurity web) throws Exception 
            web.ignoring().antMatchers("/static/**","/status");
        

        protected void configure(HttpSecurity http) throws Exception 
            http.authorizeRequests().anyRequest().hasRole("USER").and()
                .formLogin().loginPage("/login").permitAll();
        

        protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception 
            auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
        
    


这并不理想,因为我真的只想要一个身份验证机制的注册,但主要问题是它会导致以下异常:

java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found []

据我所知,@EnableGlobalMethodSecurity 设置了自己的AuthenticationManager,所以我不确定问题出在哪里。

第二种配置如下。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig 

    @Bean
    protected AuthenticationManager authenticationManager() throws Exception 
        return new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR)
                .inMemoryAuthentication()
                    .withUser("user").password("password").roles("USER").and()
                    .withUser("admin").password("password").roles("USER", "ADMIN").and()
                    .and()
                .build();
    

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter 
        @Override protected void configure(HttpSecurity http) throws Exception 
            http.antMatcher("/api/**").httpBasic();
        
    

    @Configuration
    public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 
        public void configure(WebSecurity web) throws Exception 
            web.ignoring().antMatchers("/static/**","/status");
        

        protected void configure(HttpSecurity http) throws Exception 
            http.authorizeRequests().anyRequest().hasRole("USER").and()
                .formLogin().loginPage("/login").permitAll();
        
    


此配置实际上启动成功,但有一个异常

java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required
at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:117)
at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:106)
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.performBuild(AuthenticationManagerBuilder.java:221)

当我测试时发现安全性不起作用。

我已经看了几天了,即使在深入研究 Spring Security 实现代码之后,我似乎也找不到我的配置有什么问题。

我正在使用 spring-security-3.2.0.RC1 和 spring-framework-3.2.3.RELEASE。

【问题讨论】:

你在哪里/如何加载这个MultiSecurityConfig。这是由ContextLoaderListener 加载的吗?如果是这样,安全将不起作用,因为您的 Controllers 由您的 DispatcherServlet 处理。 【参考方案1】:

当您在WebSecurityConfigurerAdapter 上使用protected registerAuthentication 方法时,它会将身份验证范围限定为WebSecurityConfigurerAdapter,因此EnableGlobalMethodSecurity 无法找到它。如果您考虑一下...这是有道理的,因为该方法受到保护。

您看到的错误实际上是一个调试语句(注意级别是 DEBUG)。原因是 Spring Security 会尝试几种不同的方式来自动连接 Global Method Security。具体EnableGlobalMethodSecurity会尝试以下方式尝试获取AuthenticationManager

如果扩展GlobalMethodSecurityConfiguration 并覆盖registerAuthentication,它将使用传入的AuthenticationManagerBuilder。这允许以与WebSecurityConfigurerAdapter 相同的方式隔离AuthenticationManager 尝试从AuthenticationManagerBuilder 的全局共享实例构建,如果失败,它会记录您所看到的错误消息(注意日志还指出“现在还可以,我们将尝试直接使用 AuthenticationManager”) 尝试使用公开为 bean 的 AuthenticationManager

对于您的代码,最好使用以下代码:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig 
    // Since MultiSecurityConfig does not extend GlobalMethodSecurityConfiguration and
    // define an AuthenticationManager, it will try using the globally defined
    // AuthenticationManagerBuilder to create one

    // The @Enable*Security annotations create a global AuthenticationManagerBuilder 
    // that can optionally be used for creating an AuthenticationManager that is shared
    // The key to using it is to use the @Autowired annotation
    @Autowired
    public void registerSharedAuthentication(AuthenticationManagerBuilder auth) throws Exception 
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
    

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter 
        // Since we didn't specify an AuthenticationManager for this class,
        // the global instance is used


        protected void configure(HttpSecurity http) throws Exception 
            http
                .antMatcher("/api/**")
                .httpBasic();
        
    

    @Configuration
    public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter 
        // Since we didn't specify an AuthenticationManager for this class,
        // the global instance is used

        public void configure(WebSecurity web) throws Exception 
            web
                .ignoring()
                    .antMatchers("/static/**","/status");
        

        protected void configure(HttpSecurity http) throws Exception 
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll();
        
    


注意:关于这方面的更多文档将在未来几天添加到参考中。

【讨论】:

谢谢 Rob,看来我的大部分问题都归结为我在使用代码配置时对范围的理解(或误解)。这让事情变得更加清晰。 后续问题 - 方法级别的安全性似乎仍然不会在此配置下触发。我已将我的类注释为需要 ADMIN 角色,但是经过身份验证的 USER 成功执行了该方法。这让我认为身份验证工作正常,而授权却没有。有什么想法吗? 我刚刚通过在此配置中创建 bean 加载了类,它识别并执行所有注释。我显然需要使用代码配置来学习上下文和范围! 不确定您所说的执行所有注释是什么意思...确定您是否发现未执行方法安全性的问题? 抱歉,“执行”是一个错误的选择。我指的是没有基于我的注释执行的方法级安全性。问题最终是我加载类的上下文与加载安全配置的位置不同

以上是关于使用 Java 配置和 Spring Security 3.2 的安全方法注释的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security OAuth - 访问此资源需要完全身份验证

容器化 Spring Boot 应用程序时出现 Docker 错误

使用Java代码和注解完成Spring配置

Java 配置中的 Spring Security XML 配置和 Spring SAML

Spring Security常用过滤器介绍

Spring中,使用Java配置的方式进行依赖注入