如何使用基于 Java 的配置启用安全注释?

Posted

技术标签:

【中文标题】如何使用基于 Java 的配置启用安全注释?【英文标题】:How to enable secured-annotations with Java based configuration? 【发布时间】:2014-09-11 23:41:47 【问题描述】:

我想为我的控制器操作使用@Secured 注释。由于我有基于 java 的配置,我需要知道如何设置

<security:global-method-security secured-annotations="enabled" />

不带 xml 文件的选项。

更新 1:

我将@EnableGlobalMethodSecurity(securedEnabled = true) 添加到我的安全配置类中:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class LIRSecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
            .authenticationProvider(preAuthenticatedAuthenticationProvider())
            .addFilter(cookiePreAuthenticationFilter())
            .authorizeRequests()
            .antMatchers("/**")
            .hasAnyAuthority("ROLE_USER")
            ;
    

    ...

在启动时会导致此异常

Jul 21, 2014 3:32:54 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodSecurityInterceptor' defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: An AuthenticationManager is required
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1512)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:633)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:410)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4937)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:695)
Caused by: java.lang.IllegalArgumentException: An AuthenticationManager is required
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterPropertiesSet(AbstractSecurityInterceptor.java:121)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
    ... 22 more
Jul 21, 2014 3:32:54 PM org.apache.catalina.core.StandardContext

更新 2:

添加后

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception 
    return super.authenticationManagerBean();

我得到另一个例外:

Caused by: org.springframework.beans.FatalBeanException: A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.validateBeanCycle(WebSecurityConfigurerAdapter.java:462)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.<init>(WebSecurityConfigurerAdapter.java:430)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.authenticationManagerBean(WebSecurityConfigurerAdapter.java:220)
    at com.galexis.lir.config.LIRSecurityConfig.authenticationManagerBean(LIRSecurityConfig.java:36)
    at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96.CGLIB$authenticationManagerBean$3(<generated>)
    at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96$$FastClassBySpringCGLIB$$a4d1ea33.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:293)
    at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96.authenticationManagerBean(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:160)
    ... 77 more

【问题讨论】:

LIRSecurityConfig 的其余部分是什么样的? 【参考方案1】:

你需要使用

@EnableGlobalMethodSecurity(securedEnabled = true)

注解,在docs中定义。

【讨论】:

【参考方案2】:

您还应该为 Manager 添加一个 bean。看看这个:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Resource(name = "authService")
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        Md5PasswordEncoder encoder = new Md5PasswordEncoder();
        auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
            .formLogin()
            .loginPage("/login")
            .and()
            .logout()
            .logoutSuccessUrl("/");
    

重要的是

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

【讨论】:

我也是这么想的,不幸的是,这会产生FatalBeanException: A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.(参见上面的更新2)。 你永远不应该使用 MD5 作为密码哈希。使用 BCryptPasswordEncoder 声明一个返回一个已经定义的bean有什么意义? Bean Override public AuthenticationManager authenticationManagerBean() throws Exception return super.authenticationManagerBean(); @Adelin 将该对象声明为 Bean,否则不会被选中。 我花了 2 天时间才找到并使用这个解决方案。对于我的(复杂)配置,AbstractSecurityInterceptor 中的 authenticationManager 为空,但未添加 authenticationManagerBean【参考方案3】:

仅适用于其他将尝试解决“尝试解析 AuthenticationManager 时检测到依赖循环。请确保您已配置身份验证。”问题。

解决方法是添加以下方法:

@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception 
    // do NOT call super.configure() !
    ...

【讨论】:

【参考方案4】:

这样就成功了:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.inMemoryAuthentication().
                withUser("user").password("user").roles("USER").and().
                withUser("admin").password("admin").roles("USER", "ADMIN");
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    

    @Override
    public void configure(WebSecurity web) throws Exception 
        super.configure(web);
    

重要的部分是

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.inMemoryAuthentication().
                withUser("user").password("user").roles("USER").and().
                withUser("admin").password("admin").roles("USER", "ADMIN");
    

【讨论】:

AuthenticationManager 添加@Bean 为我做到了。我没有configureGlobal() 方法,尽管我确实有一个用于 AuthenticationManagerBuilder 的配置方法,其中包含一个 UserDetailsS​​ervice。【参考方案5】:

Stefan 是对的,添加了

@EnableGlobalMethodSecurity(securedEnabled = true)

成功了。

在我的特殊情况下,我必须添加以摆脱 excptions。

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
    auth
        .inMemoryAuthentication()
            .withUser("user").password("password").authorities("ROLE_USER");

【讨论】:

【参考方案6】:

关注注释中的类

 @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SecurityConfigProvider extends GlobalMethodSecurityConfiguration 

        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() 
            return new OAuth2MethodSecurityExpressionHandler();
        

    


@Configuration
@EnableOAuth2Client
@EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        //TODO
    

    @Override
    public void configure(WebSecurity web) throws Exception 
        //TODO
    

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    


【讨论】:

【参考方案7】:

导入 org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@EnableGlobalMethodSecurity(prePostEnabled=true)

【讨论】:

以上是关于如何使用基于 Java 的配置启用安全注释?的主要内容,如果未能解决你的问题,请参考以下文章

如何使这个线程安全

如何在类级别注释中为 JavaDocs 启用 Java checkstyle?

基于 Java 注解的 Spring 安全配置

无法使用 Spring 安全登录 - 使用外部数据库的基于注释的配置

如何做基于OS Profile的Protractor配置

使用Java,Spring进行基于配置文件,可切换的JSON缩小