【问题标题】:Spring Security OAuth2/OIDC RP-initiated logout does not workSpring Security OAuth2/OIDC RP 发起的注销不起作用
【发布时间】:2022-02-04 17:23:23
【问题描述】:

我正在尝试使用 Spring Security 实现 RP 发起的注销,从 Spring Cloud Gateway 到 Keycloak。我的 Spring Security 配置与Spring Security Reference document 中提供的配置几乎相同,转载如下,

@EnableWebFluxSecurity
public class SCGSecurityConfig {
    
    @Autowired
    private ReactiveClientRegistrationRepository clientRegistrationRepository;

    private ServerLogoutSuccessHandler keycloakLogoutSuccessHandler() {

        OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
                new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);

        // Sets the location that the End-User's User Agent will be redirected to
        // after the logout has been performed at the Provider
        oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");

        return oidcLogoutSuccessHandler;
    }
    
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {

        http.authorizeExchange(authorize -> authorize.anyExchange().authenticated())
            .oauth2Login(withDefaults())
            .logout(logout -> logout.logoutSuccessHandler(keycloakLogoutSuccessHandler()));
                    
        //need to disable on gateway, since we have backend services
        http.csrf().disable();

        return http.build();
    }
}

点击 /logout 端点只会在网关上进行本地注销。我看到没有流量到 Keycloak 以在 OP 上注销。 Keycloak 控制台显示用户会话仍处于活动状态。 Keycloaks Discovery Metadata 确实将 end_session_endpoint 显示为 true。

这是 Spring Security 的相关日志记录。我没有看到任何 TRACE 级别的日志,只有 DEBUG(可能代码路径没有命中任何 TRACE 消息?)

04:01:27.027 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'GET /logout' doesn't match 'null /oauth2/authorization/{registrationId}'
04:01:27.027 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'GET /logout' doesn't match 'null /login/oauth2/code/{registrationId}'
04:01:27.027 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/login', method=GET}
04:01:27.027 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'GET /logout' doesn't match 'GET /login'
04:01:27.027 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: No matches found
04:01:27.027 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=GET}
04:01:27.027 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/logout'; against '/logout'
04:01:27.027 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: matched
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'POST /logout' doesn't match 'null /oauth2/authorization/{registrationId}'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'POST /logout' doesn't match 'null /login/oauth2/code/{registrationId}'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/login', method=GET}
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'POST /logout' doesn't match 'GET /login'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: No matches found
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=GET}
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'POST /logout' doesn't match 'GET /logout'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: No matches found
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST}
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/logout'; against '/logout'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: matched
04:01:29.029 reactor-http-epoll-2 DEBUG anduril WebSessionServerSecurityContextRepository: Found SecurityContext 'SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [john@acme.com], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_profile]], User Attributes: [{sub=400d12ab-dd15-47ab-b023-35a046e28a75, email_verified=true, name=John Doe, preferred_username=john@acme.com, given_name=John, family_name=Doe, email=john@acme.com}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_profile]]]' in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@142a2883'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril LogoutWebFilter: Logging out user 'OAuth2AuthenticationToken [Principal=Name: [john@acme.com], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_profile]], User Attributes: [{sub=400d12ab-dd15-47ab-b023-35a046e28a75, email_verified=true, name=John Doe, preferred_username=john@acme.com, given_name=John, family_name=Doe, email=john@acme.com}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_profile]]' and transferring to logout destination
04:01:29.029 reactor-http-epoll-2 DEBUG anduril WebSessionServerSecurityContextRepository: Removed SecurityContext stored in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@142a2883'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril DefaultServerRedirectStrategy: Redirecting to '/login?logout'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'GET /login' doesn't match 'null /oauth2/authorization/{registrationId}'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Request 'GET /login' doesn't match 'null /login/oauth2/code/{registrationId}'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/login', method=GET}
04:01:29.029 reactor-http-epoll-2 DEBUG anduril PathPatternParserServerWebExchangeMatcher: Checking match of request : '/login'; against '/login'
04:01:29.029 reactor-http-epoll-2 DEBUG anduril OrServerWebExchangeMatcher: matched

这是 application.yml 的编辑版本,

logging.level:
  reactor.netty: INFO
  org.springframework.cloud.gateway: INFO
  org.springframework.security: TRACE
  org.springframework.web.FilterChainProxy: INFO
  org.springframework.web.reactive.function.client: INFO
spring.cloud.gateway:
  httpclient:
    wiretap: true
  httpserver:
    wiretap: true
  default-filters:
  - name: BasicAuthFilter
  routes:
    - id: adminservice
      uri: http://${ADMIN_SERVICE}/
      predicates:
        - Path=/admin/**
    - id: appservice
      uri: http://${APP_SERVICE}/
      predicates:
        - Path=/app/**
spring.security.oauth2.client:
  provider:
    keycloak:
      issuer-uri: http://${AUTHSERVER}/auth/realms/${REALM}
      user-name-attribute: preferred_username
  registration:
    keycloak-registration:
      provider: keycloak
      client-id: ${CLIENT_ID}
      client-secret: ${CLIENT_SECRET}
      authorization-grant-type: authorization_code
      redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

【问题讨论】:

  • 请将 org.springframework.security 的日志级别提高到 TRACE 并显示当您点击 /logout 端点时发出的日志。
  • 嗨@jzheaux。我已经添加了日志和 application.yml。我没有看到任何 TRACE 消息;只有调试。谢谢。
  • @TusharDesai 我在设置中观察到相同的行为。您对此问题有任何更新吗?
  • @RobertStrauch 没有更新。从来没有回音。所以,我打算改为评估 KrakenD。
  • 我发布了一个对我有用的答案。

标签: spring-boot spring-security keycloak spring-cloud-gateway


【解决方案1】:

您可以尝试更新您的application.yml 并设置一个显式的scope: openid(以及您的客户需要的任何其他范围)吗?

spring.security.oauth2.client:
  provider:
    keycloak:
      issuer-uri: http://${AUTHSERVER}/auth/realms/${REALM}
      user-name-attribute: preferred_username
  registration:
    keycloak-registration:
      provider: keycloak
      client-id: ${CLIENT_ID}
      client-secret: ${CLIENT_SECRET}
      scope: openid
      authorization-grant-type: authorization_code
      redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"

this issue in Spring Security 正好解决了这个问题。另请参阅my comment to this issue

我对这个问题的理解如下:

  • 范围参数对于 OAuth2 规范是可选的。这就是 Spring Security 允许它为空/null 并且不会抱怨的原因。
  • 但是,oauth2Login() 和 logout() 功能,例如OidcClientInitiatedServerLogoutSuccessHandler,要求范围参数至少为 openid。

我还不确定我的理解是否正确。

【讨论】:

    猜你喜欢
    • 2021-07-04
    • 2017-01-13
    • 2018-02-05
    • 2011-10-05
    • 2013-05-24
    • 1970-01-01
    • 2015-06-28
    • 2011-08-20
    • 2020-10-20
    相关资源
    最近更新 更多