从 Spring Security OAuth2 迁移到 Keycloak 时,如何解决“permitAll 仅适用于 HttpSecurity.authorizeRequests()”?

Posted

技术标签:

【中文标题】从 Spring Security OAuth2 迁移到 Keycloak 时,如何解决“permitAll 仅适用于 HttpSecurity.authorizeRequests()”?【英文标题】:How to solve `permitAll only works with HttpSecurity.authorizeRequests()` when migrating from Spring Security OAuth2 to Keycloak? 【发布时间】:2020-05-12 03:29:34 【问题描述】:

我目前正在从 Spring Security OAuth2 迁移到 Keycloak(在 Spring Security 团队决定弃用 Spring Security OAuth2 项目之后),但我遇到了这个例外:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: permitAll only works with HttpSecurity.authorizeRequests()
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:484)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$330.00000000ED7F64A0.getObject(Unknown Source)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)

我有 10 个微服务要迁移,我定义了一个包含所有配置的 common jar。这是common中的Keycloak配置:

@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
@KeycloakConfiguration
@Import( KeycloakSpringBootConfigResolver.class )
public class CommonsKeycloakSecurityConfigurerAdapter extends KeycloakWebSecurityConfigurerAdapter 

@Override
protected void configure( HttpSecurity http ) throws Exception 
    http.oauth2ResourceServer(); // Equivalent to @EnableResourceServer in S.S. OAuth2
    super.configure( http );



@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) 

    SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
    grantedAuthorityMapper.setPrefix( "ROLE_" ); 
    grantedAuthorityMapper.setConvertToUpperCase( true );

    KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper( grantedAuthorityMapper );
    auth.authenticationProvider( keycloakAuthenticationProvider );


@Value("$spring.application.name"
public String APP_NAME;

@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategyPorvider() 
    return APP_NAME.equals( "securityservice")
            ? new RegisterSessionAuthenticationStrategy( new SessionRegistryImpl() )
            : new NullAuthenticatedSessionStrategy();


@Bean
@DependsOn( "sessionAuthenticationStrategyPorvider" )
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() 
    return sessionAuthenticationStrategyPorvider();


@Bean
@Scope( scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS )
public KeycloakSecurityContext provideKeycloakSecurityContext() 
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    Principal principal = requireNonNull( attributes ).getRequest().getUserPrincipal();
    if ( principal == null ) 
        return null;
    
    if ( principal instanceof KeycloakAuthenticationToken ) 
        principal = (Principal) ( (KeycloakAuthenticationToken) principal ).getPrincipal();
    
    if ( principal instanceof KeycloakPrincipal ) 
        return ( (KeycloakPrincipal) principal ).getKeycloakSecurityContext();
    
    return null;

以及common/src/main/resources/application.yml中的属性:

keycloak:
  auth-server-url: "" #defined in application-dev.yml and application-prod.yml
  realm: $project.name
  resource: $spring.application.name
  credentials:
    secret: $application.kck.secret 
  use-resource-role-mappings: true
  ssl-required: 'none'
  principal-attribute: preferred_username

然后将common.jar 导入所有其他微服务中。在productservice 中,我定义了这些特定的http 安全规则:

@Configuration
@Order( 1 )
public class UIResourceProtection extends WebSecurityConfigurerAdapter  // Note that I use WebSecurityConfigurerAdapter instead of KeycloakWebSecurityConfigurerAdapter 

    @Override
    public void configure( HttpSecurity http ) throws Exception 
        http.sessionManagement().sessionCreationPolicy( STATELESS );
        http.requestMatchers().antMatchers( "/ui/product/**" )
                .and()
                .cors().and()
                .authorizeRequests()
                .antMatchers( "/ui/product/private").hasRole( "USER" )
                .antMatchers( HttpMethod.PUT, "/ui/product/public" ).hasRole( "USER" )
                .antMatchers( HttpMethod.GET, ""/ui/product/cost"","/ui/product/public").authenticated()
                .antMatchers( HttpMethod.GET, "/ui/product/public/id" ).authenticated()
                .antMatchers( "/ui/product/public/dashboard" ).hasRole( "USER" );                    
    

和同一个项目中的另一个(即:productservice

@Configuration
@RequiredArgsConstructor
@Order( 2 )
public class SelfResourceProtection extends WebSecurityConfigurerAdapter  // Note that I use WebSecurityConfigurerAdapter instead of KeycloakWebSecurityConfigurerAdapter 

    @Override
    public void configure( HttpSecurity http ) throws Exception 
        http.requestMatchers().antMatchers("/product/**")
                .and()
                .authorizeRequests()
                .antMatchers( HttpMethod.GET, "/product/update-db" ).hasRole( "ADMIN" )
                .antMatchers( HttpMethod.GET, "/product/private/id" ).permitAll();     
    

当我使用该配置启动 productservice 时,出现上述异常:Caused by: java.lang.IllegalStateException: permitAll only works with HttpSecurity.authorizeRequests()

更改配置参数后,仍然抛出异常。

知道怎么解决吗?

依赖版本:

Spring Boot : 2.2.4.RELEASE
Keycloak : 8.0.1
Spring Security: 5.2.1.RELEASE
Spring Security OAuth2 Resource Server: 5.2.1.RELEASE

非常感谢

【问题讨论】:

【参考方案1】:

这个异常是由类CommonsKeycloakSecurityConfigurerAdapter引起的。 如果您查看正在扩展的KeycloakWebSecurityConfigurerAdapter 的源代码,您会注意到部分配置状态

.logoutUrl("/sso/logout").permitAll()

为了应用permitAll(),Spring Security 需要配置authorizeRequests()

由于KeycloakWebSecurityConfigurerAdapter 中没有此类配置,您需要将authorizeRequests() 块添加到扩展它的自定义配置中。

【讨论】:

谢谢,事实上这似乎是问题的原因,但我不明白为什么 Spring Security 会从该配置中抛出异常,因为他们的 api 计划像那样使用。怪怪的。我只是删除了对super.configure(http) 的调用。我的配置还没有工作,但我现在可以继续。谢谢 @akuma8,Spring Security 不打算像您描述的那样使用(调用 permitAll 而不调用 authorizeRequests)。 permitAll 描述了访问条件,authorizeRequests 是告诉 Spring Security 做出访问决策的原因。如果没有authorizeRequests,就没有配置任何东西来实际执行访问条件。 Keycloak 期待您致电 authorizeRequests,如他们的文档中所述:keycloak.org/docs/latest/securing_apps/… @jzheaux 对于在 github 中打开的问题感到抱歉,但是在不知道发生了什么的情况下转身有点令人沮丧。那么问题来自Keycloak配置?你建议我这个I'm wondering why you are using the Keycloak adapter in this case? Since Keycloak's authorization server is compliant with several OAuth 2.0 RFCs, I'd imagine that using Spring Security native would do the trick 。我的问题是如何将 Spring Security 本机与 Keycloak 一起使用?使用 Spring Security OAuth2 对我来说似乎很清楚,但使用 S.S. 5.2.1.RELEASE 则根本不清楚。我不明白怎么做。谢谢 @akuma8 没问题。为了简单起见,让我们看看是否可以拆分两个 convos。我认为您在这里发布的问题已经得到解答,所以也许我们不要在这里发布,除非它是关于原始问题的。 @jzheaux 对,我会在 Gitter 上联系你,希望你能到。谢谢

以上是关于从 Spring Security OAuth2 迁移到 Keycloak 时,如何解决“permitAll 仅适用于 HttpSecurity.authorizeRequests()”?的主要内容,如果未能解决你的问题,请参考以下文章

从Spring Security Oauth2中的Token Store检索访问和刷新令牌的方法

Spring boot security oauth2 从 cookie 获取 access_token

从 Spring Security OAuth2 迁移到 Keycloak 时,如何解决“permitAll 仅适用于 HttpSecurity.authorizeRequests()”?

Spring Security OAuth2 accessToken

spring security oauth2 发布限制

Spring Security Oauth2 - 向 AccessTokenRequest 添加凭据