Spring Security中OpenID/SAML如何传递初始请求参数至最终重定向
Spring Security 传递初始请求查询参数到最终重定向地址的实现方案
Spring Security 完全支持在服务发起式认证流(OpenID/SAML)中保留初始请求的查询参数,核心思路是在发起授权请求时保存参数,认证成功后取出并拼接回最终重定向URL,以下是两种场景的简便实现方式:
一、OpenID Connect 场景实现
1. 自定义授权请求解析器,保存初始查询参数
通过自定义OAuth2AuthorizationRequestResolver,将初始请求的查询参数添加到授权请求的额外参数中并存入会话:
@Bean public OAuth2AuthorizationRequestResolver customOidcAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI ); resolver.setAuthorizationRequestCustomizer(request -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) return; HttpServletRequest servletRequest = attributes.getRequest(); // 提取初始请求的所有查询参数 Map<String, String> queryParams = servletRequest.getParameterMap().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()[0])); // 将参数添加到授权请求的额外参数中 request.additionalParameters(queryParams); }); return resolver; }
2. 自定义认证成功处理器,拼接参数到重定向URL
重写SimpleUrlAuthenticationSuccessHandler,从会话中取出保存的参数,拼接回最终重定向地址:
@Bean public AuthenticationSuccessHandler customOidcSuccessHandler() { return new SimpleUrlAuthenticationSuccessHandler() { @Override protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { String targetUrl = super.determineTargetUrl(request, response, authentication); if (authentication instanceof OAuth2AuthenticationToken oauthToken) { // 从会话中获取之前保存的授权请求 OAuth2AuthorizationRequest authRequest = (OAuth2AuthorizationRequest) request.getSession() .getAttribute(OAuth2AuthorizationRequestRepository.DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME); if (authRequest != null && !authRequest.getAdditionalParameters().isEmpty()) { // 拼接参数到目标URL UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(targetUrl); authRequest.getAdditionalParameters().forEach(uriBuilder::queryParam); targetUrl = uriBuilder.build().toUriString(); } } return targetUrl; } }; }
3. 配置SecurityFilterChain,注册自定义组件
将上述组件配置到Spring Security的过滤器链中:
@Bean public SecurityFilterChain oidcSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .authorizationRequestResolver(customOidcAuthorizationRequestResolver(clientRegistrationRepository()))) .successHandler(customOidcSuccessHandler()) ); return http.build(); }
二、SAML2 场景实现
SAML2 可利用relayState参数传递自定义数据,实现逻辑类似:
1. 自定义SAML认证请求解析器,保存参数到relayState
@Bean public Saml2AuthenticationRequestResolver customSamlAuthenticationRequestResolver(RelyingPartyRegistrationRepository repo) { DefaultSaml2AuthenticationRequestResolver resolver = new DefaultSaml2AuthenticationRequestResolver(repo); resolver.setAuthenticationRequestCustomizer(request -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) return; HttpServletRequest servletRequest = attributes.getRequest(); Map<String, String> queryParams = servletRequest.getParameterMap().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()[0])); // 将参数序列化为JSON后存入relayState try { String relayState = new ObjectMapper().writeValueAsString(queryParams); request.setRelayState(relayState); } catch (JsonProcessingException e) { // 异常处理逻辑 } }); return resolver; }
2. 自定义认证成功处理器,从relayState取出参数拼接URL
@Bean public AuthenticationSuccessHandler customSamlSuccessHandler() { return new SimpleUrlAuthenticationSuccessHandler() { @Override protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { String targetUrl = super.determineTargetUrl(request, response, authentication); if (authentication instanceof Saml2AuthenticationToken samlToken) { String relayState = samlToken.getRelayState(); if (relayState == null) return targetUrl; try { // 反序列化relayState中的参数 Map<String, String> queryParams = new ObjectMapper() .readValue(relayState, new TypeReference<Map<String, String>>() {}); // 拼接参数到目标URL UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(targetUrl); queryParams.forEach(uriBuilder::queryParam); targetUrl = uriBuilder.build().toUriString(); } catch (JsonProcessingException e) { // 异常处理逻辑 } } return targetUrl; } }; }
3. 配置SAML2的SecurityFilterChain
@Bean public SecurityFilterChain samlSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .saml2Login(saml2 -> saml2 .authenticationRequestResolver(customSamlAuthenticationRequestResolver(relyingPartyRegistrationRepository())) .successHandler(customSamlSuccessHandler()) ); return http.build(); }
核心原理说明
- 参数保存阶段:在跳转至身份提供商(IdP)前,将初始请求的查询参数存入授权请求的额外参数(OpenID)或SAML的
relayState中,Spring Security会自动处理这些数据的传递(OpenID通过state参数加密传递,SAML直接通过RelayState参数传递)。 - 参数恢复阶段:认证成功后,从认证令牌或会话中取出保存的参数,拼接回最终的重定向URL,实现参数的全程传递。
内容的提问来源于stack exchange,提问作者PentaKon
相关产品推荐
相关产品推荐

