Spring Security Oauth2 + OIDC (OpenID Connect) OP 发起/返回通道注销与多个 RP

问题描述

具有多个 RP 的身份验证服务器 (OP) 是架构。请检查下面的图片

enter image description here

我能够使用 RP initiated logout 成功注销,也就是说,如果我从任何客户端注销,则客户端 (RP) 会被注销并且 OP 也会被注销。

请找到 OpenID Provider discovery Metadata;

{
    "jwks_uri":"https://<Auth Server URL>/token_keys","subject_types_supported":["public"],"end_session_endpoint":"https://<Auth Server URL>/logout","issuer":"https://<Auth Server URL>/oauth/token","authorization_endpoint":"https://<Auth Server URL>/oauth/authorize","token_endpoint":"https://<Auth Server URL>/oauth/token"
}

在发现中存在 "end_session_endpoint" 时,我能够使用客户端中的以下代码执行 RP 发起的注销,

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcclientinitiatedlogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.web.csrf.CookieCsrftokenRepository;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import com.nimbusds.jose.shaded.json.JSONArray;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**").antMatchers("/assets/bundles/**");
    }
    
     @Bean(name = "oidcUserService")
     OAuth2UserService<OidcUserRequest,OidcUser> oidcUserService() {
         return new CustomOidcUserService();
     }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        http.cors().and().headers(headers -> headers.cacheControl().and()
                .addHeaderWriter(new StaticHeadersWriter("X-UA-Compatible","IE=edge"))
                .frameOptions(frameOptions -> frameOptions.sameOrigin()).httpStrictTransportSecurity())
        .authorizeRequests(authorize -> authorize
                .antMatchers("/error").permitAll()
                .anyRequest().authenticated())
        .oauth2Login(oauthLogin -> oauthLogin
                .userInfoEndpoint()
                .oidcUserService(this.oidcUserService()))
        .logout(logout -> logout
                .logoutSuccessHandler(oidclogoutSuccessHandler())
                .invalidateHttpSession(true)
                .clearauthentication(true)
                .deleteCookies("JSESSIONID","CSRF-TOKEN","XSRF-TOKEN")
                .permitAll())
         .csrf((csrf) -> csrf
                    .csrftokenRepository(CookieCsrftokenRepository.withHttpOnlyFalse()));

    }

    private OidcclientinitiatedlogoutSuccessHandler oidclogoutSuccessHandler() {
        OidcclientinitiatedlogoutSuccessHandler oidclogoutSuccessHandler = new OidcclientinitiatedlogoutSuccessHandler(this.clientRegistrationRepository);
        oidclogoutSuccessHandler.setPostlogoutRedirectUri("{baseUrl}");
        return oidclogoutSuccessHandler;
     }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","PATCH","DELETE","OPTIONS"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
        return source;
    }

}

但我不知道如何实现 OP 启动的 logot,如果 OP 会话过期或 OP 被注销,它会注销 OP 下的所有客户端。有没有可用的例子?我已经为此研究了几天,发现有另一个名为 "check_session_iframe" 的密钥,但找不到来自 spring 的正确文档。如果您有任何示例,请分享。谢谢。

解决方法

如果 OP 的元数据不包含 df['id'] = df['id'].fillna(0).astype(int) check_session_iframefrontchannel_logout_supported,那么它根本不支持单点注销,这将在客户端触发注销,即“OP 发起的注销” ”。


backchannel_logout_supported 包含在 OP 控制下的 URL。客户端会将此 URL 嵌入为 iframe,也称为 OP iframe。 OP iframe 可以访问 OpenID 提供程序的用户代理状态(因为它是同一个域)。客户端将创建另一个 iframe,即 RP iframe,将 postMessage 请求发送到 OP iframe 以检查会话状态。然后,OP iframe 将向 RP iframe 发送 postMessage 请求,并返回结果(已更改、未更改或错误)。

关于会话管理规范的客户端部分,您唯一需要做的就是嵌入 OP iframe,给它一个 id,以便您可以向它发送 postMessage 请求并在您的 RP iframe 中处理来自它的 postMessage 请求。查看规范中的伪代码: https://openid.net/specs/openid-connect-session-1_0.html#RPiframe