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

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

I am having some issues getting my application set up using method level annotation controlled by @EnableGlobalMethodSecurity I am using Servlet 3.0 style initialization using

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(MultiSecurityConfig.class);
    }
}

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

I have attempted 2 different ways of initialising an AuthenticationManager both with their own issues. Please note that not using @EnableGlobalMethodSecurity results in a successful server start up and all of the form security executes as expected. My issues arise when I add @EnableGlobalMethodSecurity and @PreAuthorize("hasRole('ROLE_USER')") annotations on my controller.

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

I am attempting to set up form-based and api-based security independently. The method based annotations need only work for the api security.

一种配置如下.

@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");
        }
    }

}

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

This is not ideal as I really want only a single registration of the authentication mechanism but the main issue is that it results in the following exception:

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

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

As far as I am aware @EnableGlobalMethodSecurity sets up its own AuthenticationManager so I'm not sure what the problem is here.

第二种配置如下.

@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();
        }
    }

}

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

This config actually starts successfully but with an exception

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)

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

and when I test I found that the security doesn't work.

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

I've been looking at this for a couple of days now and even after diving into spring security implementation code I can't seem to find what is wrong with my configuration.

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

I am using spring-security-3.2.0.RC1 and spring-framework-3.2.3.RELEASE.

推荐答案

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

When you use the protected registerAuthentication methods on WebSecurityConfigurerAdapter it is scoping the Authentication to that WebSecurityConfigurerAdapter so EnableGlobalMethodSecurity cannot find it. If you think about this...it makes sense since the method is protected.

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

The error you are seeing is actually a debug statement (note the level is DEBUG). The reason is that Spring Security will try a few different ways to automatically wire the Global Method Security. Specifically EnableGlobalMethodSecurity will try the following ways to try and get the AuthenticationManager:

  • 如果您扩展 GlobalMethodSecurityConfiguration 并覆盖 registerAuthentication 它将使用传入的 AuthenticationManagerBuilder.这允许隔离 AuthenticationManagerWebSecurityConfigurerAdapter
  • 一样
  • 尝试从 AuthenticationManagerBuilder 的全局共享实例构建,如果失败,它会记录您所看到的错误消息(注意日志还指出现在可以,我们将尝试使用AuthenticationManager 直接")
  • 尝试使用作为 bean 公开的 AuthenticationManager.
  • If you extend GlobalMethodSecurityConfiguration and override the registerAuthentication it will use the AuthenticationManagerBuilder that was passed in. This allows for isolating the AuthenticationManager in the same way you can do so with WebSecurityConfigurerAdapter
  • Try to build from the global shared instance of AuthenticationManagerBuilder, if it fails it logs the error message you are seeing (Note the logs also state "This is ok for now, we will try using an AuthenticationManager directly")
  • Try to use an AuthenticationManager that is exposed as a bean.

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

For your code, you are going to be best off using something like the following:

@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();
        }
    }

}

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

NOTE: More documentation around this will be getting added to the reference in the coming days.

相关文章