OIDC 登出

一旦最终用户能够登录到您的应用程序,考虑他们将如何注销就很重要。

通常来说,有三种用例需要您考虑:

  1. 我只想执行本地注销

  2. 我想同时注销我的应用程序和 OIDC 提供者,由我的应用程序发起

  3. 我想同时注销我的应用程序和 OIDC 提供者,由 OIDC 提供者发起

本地注销

要执行本地注销,不需要特殊的 OIDC 配置。Spring Security 会自动建立一个本地注销端点,您可以通过logout() DSL 进行配置

OpenID Connect 1.0 客户端发起注销

OpenID Connect Session Management 1.0 允许客户端在提供者处注销最终用户。其中一种可用策略是RP-发起注销

如果 OpenID 提供者同时支持会话管理和发现,客户端可以从 OpenID 提供者的发现元数据中获取 end_session_endpoint URL。您可以通过配置 ClientRegistrationissuer-uri 来实现,如下所示:

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            ...
        provider:
          okta:
            issuer-uri: https://dev-1234.oktapreview.com

此外,您应该配置 OidcClientInitiatedServerLogoutSuccessHandler,它实现了 RP-发起注销,如下所示:

  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

	@Autowired
	private ReactiveClientRegistrationRepository clientRegistrationRepository;

	@Bean
	public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2Login(withDefaults())
			.logout((logout) -> logout
				.logoutSuccessHandler(oidcLogoutSuccessHandler())
			);
		return http.build();
	}

	private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
		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;
	}
}
@Configuration
@EnableWebFluxSecurity
class OAuth2LoginSecurityConfig {
    @Autowired
    private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository

    @Bean
    open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http {
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            oauth2Login { }
            logout {
                logoutSuccessHandler = oidcLogoutSuccessHandler()
            }
        }
        return http.build()
    }

    private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
        val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(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
    }
}

OidcClientInitiatedServerLogoutSuccessHandler 支持 {baseUrl} 占位符。如果使用,应用程序的基本 URL,例如 app.example.org,将在请求时替换它。

默认情况下,OidcClientInitiatedServerLogoutSuccessHandler 使用带有 GET 方法的标准 HTTP 重定向将请求重定向到注销 URL。要使用 POST 请求执行注销,请将重定向策略设置为 FormPostServerRedirectStrategy,例如通过 OidcClientInitiatedServerLogoutSuccessHandler.setRedirectStrategy(new ServerFormPostRedirectStrategy())

OpenID Connect 1.0 后端通道注销

OpenID Connect Session Management 1.0 允许提供者通过向客户端发出 API 调用来注销客户端的最终用户。这被称为OIDC 后端通道注销

要启用此功能,您可以在 DSL 中设置后端通道注销端点,如下所示:

  • Java

  • Kotlin

@Bean
OidcBackChannelServerLogoutHandler oidcLogoutHandler() {
	return new OidcBackChannelServerLogoutHandler();
}

@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
    http
        .authorizeExchange((authorize) -> authorize
            .anyExchange().authenticated()
        )
        .oauth2Login(withDefaults())
        .oidcLogout((logout) -> logout
            .backChannel(Customizer.withDefaults())
        );
    return http.build();
}
@Bean
fun oidcLogoutHandler(): OidcBackChannelLogoutHandler {
    return OidcBackChannelLogoutHandler()
}

@Bean
open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    http {
        authorizeExchange {
            authorize(anyExchange, authenticated)
        }
        oauth2Login { }
        oidcLogout {
            backChannel { }
        }
    }
    return http.build()
}

就是这样!

这将建立一个端点 /logout/connect/back-channel/{registrationId},OIDC 提供者可以请求该端点来使您应用程序中给定最终用户的会话失效。

oidcLogout 要求也配置 oauth2Login
oidcLogout 要求会话 cookie 被命名为 JSESSIONID,以便通过后端通道正确注销每个会话。

后端通道注销架构

考虑一个标识符为 registrationIdClientRegistration

后端通道注销的总体流程如下:

  1. 在登录时,Spring Security 将 ID 令牌、CSRF 令牌和提供者会话 ID(如果有)与您应用程序的会话 ID 关联在其 ReactiveOidcSessionRegistry 实现中。

  2. 然后在注销时,您的 OIDC 提供者向 /logout/connect/back-channel/registrationId 发出 API 调用,其中包含一个注销令牌,指示要注销的 sub(最终用户)或 sid(提供者会话 ID)。

  3. Spring Security 验证令牌的签名和声明。

  4. 如果令牌包含 sid 声明,则只会终止与该提供者会话相关的客户端会话。

  5. 否则,如果令牌包含 sub 声明,则将终止该最终用户的所有客户端会话。

请记住,Spring Security 的 OIDC 支持是多租户的。这意味着它只会终止其客户端与注销令牌中的 aud 声明匹配的会话。

自定义会话注销端点

发布 OidcBackChannelServerLogoutHandler 后,会话注销端点为 {baseUrl}/logout/connect/back-channel/{registrationId}

如果未配置 OidcBackChannelServerLogoutHandler,则 URL 为 {baseUrl}/logout/connect/back-channel/{registrationId},不建议这样做,因为它需要传递 CSRF 令牌,这可能根据您的应用程序使用的存储库类型而具有挑战性。

如果您需要自定义端点,可以按如下方式提供 URL:

  • Java

  • Kotlin

http
    // ...
    .oidcLogout((oidc) -> oidc
        .backChannel((backChannel) -> backChannel
            .logoutUri("https://:9000/logout/connect/back-channel/+{registrationId}+")
        )
    );
http {
    oidcLogout {
        backChannel {
            logoutUri = "https://:9000/logout/connect/back-channel/+{registrationId}+"
        }
    }
}

默认情况下,会话注销端点使用 JSESSIONID cookie 将会话与相应的 OidcSessionInformation 相关联。

但是,Spring Session 中的默认 cookie 名称是 SESSION

您可以在 DSL 中配置 Spring Session 的 cookie 名称,如下所示:

  • Java

  • Kotlin

@Bean
OidcBackChannelServerLogoutHandler oidcLogoutHandler(ReactiveOidcSessionRegistry sessionRegistry) {
    OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(sessionRegistry);
    logoutHandler.setSessionCookieName("SESSION");
    return logoutHandler;
}
@Bean
open fun oidcLogoutHandler(val sessionRegistry: ReactiveOidcSessionRegistry): OidcBackChannelServerLogoutHandler {
    val logoutHandler = OidcBackChannelServerLogoutHandler(sessionRegistry)
    logoutHandler.setSessionCookieName("SESSION")
    return logoutHandler
}

自定义 OIDC 提供者会话注册表

默认情况下,Spring Security 在内存中存储 OIDC 提供者会话和客户端会话之间的所有链接。

在某些情况下,例如集群应用程序,将此信息存储在单独的位置(例如数据库)会更好。

您可以通过配置自定义的 ReactiveOidcSessionRegistry 来实现此目的,如下所示:

  • Java

  • Kotlin

@Component
public final class MySpringDataOidcSessionRegistry implements ReactiveOidcSessionRegistry {
    private final OidcProviderSessionRepository sessions;

    // ...

    @Override
    public Mono<void> saveSessionInformation(OidcSessionInformation info) {
        return this.sessions.save(info);
    }

    @Override
    public Mono<OidcSessionInformation> removeSessionInformation(String clientSessionId) {
       return this.sessions.removeByClientSessionId(clientSessionId);
    }

    @Override
    public Flux<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
        return token.getSessionId() != null ?
            this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
            this.sessions.removeBySubjectAndIssuerAndAudience(...);
    }
}
@Component
class MySpringDataOidcSessionRegistry: ReactiveOidcSessionRegistry {
    val sessions: OidcProviderSessionRepository

    // ...

    @Override
    fun saveSessionInformation(info: OidcSessionInformation): Mono<Void> {
        return this.sessions.save(info)
    }

    @Override
    fun removeSessionInformation(clientSessionId: String): Mono<OidcSessionInformation> {
       return this.sessions.removeByClientSessionId(clientSessionId);
    }

    @Override
    fun removeSessionInformation(token: OidcLogoutToken): Flux<OidcSessionInformation> {
        return token.getSessionId() != null ?
            this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
            this.sessions.removeBySubjectAndIssuerAndAudience(...);
    }
}
© . This site is unofficial and not affiliated with VMware.