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.