You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨域场景下React与集成Okta SAML的Spring Boot后端集成问题求助

跨域集成React前端(UI.COM)与Spring Boot SAML认证后端(Api.com)问题排查

后端基于Spring Security实现SAML认证,已创建返回用户信息的https://api.com/user接口,以及处理重定向的https://api.com/private控制器。

方案A

  • 前端调用自身接口检查用户信息(首次无信息,执行后续步骤)
  • 前端重定向至后端/private接口
  • 后端重定向至Okta获取SAML响应
  • Spring Security解析响应,启动BackendSession并生成SamlUserDetails
  • 后端重定向回前端,同时在浏览器写入对应后端域名的BackendSessionId Cookie
    问题:前端无法读取该后端域名下的BackendSessionId
  • 后续需调用后端/user接口获取用户信息,该接口需通过BackendSessionId验证SamlUserDetails

方案B

步骤1-4与方案A一致

  • 后端重定向回前端时,将BackendSessionId附加到URL查询参数,同时写入对应后端域名的Cookie
  • 前端通过查询参数获取BackendSessionId
  • 前端调用后端/user接口时将BackendSessionId放入请求头
    问题:后端/user接口无法通过该SessionId获取SamlUserDetails

注:客户端完全不想使用Cookie,已排除写入Cookie的方案


代码参考

重定向控制器(https://api.com/private)
@GetMapping("/private")
public ModelAndView index(@SAMLUser SAMLUserDetails user, HttpServletRequest request, HttpServletResponse response){
    Map<String, String> userAttributes = user.getAttributes();
    log.info("userAttributes {}", userAttributes);
    log.info("saml user {}", user);
    log.info("saml userName {}", user.getUsername());
    String location = request.getHeader("referer");
    if (isBlank(location)){
        location=srtUiUrl;
    }
    StringBuilder sb = new StringBuilder();
    sb.append(location);
    sb.append("?stateId=");
    sb.append(request.getSession().getId());
    log.info("RedirectUrl ",sb.toString());
    return new ModelAndView("redirect:" + sb.toString());
}
用户信息接口(https://api.com/user)
@GetMapping("/receive/v1/user")
public ResponseEntity<User> getUser(@SAMLUser SAMLUserDetails user) {
    log.info("SAMLUserDetails : ", user);

    if (null==user) {
        log.info("No user found Authentication required");
        return new ResponseEntity<>(null, HttpStatus.OK);
    } else {
            Map<String, String> userAttributes = user.getAttributes();
            log.info("SAMLUserDetails Attributes {}", userAttributes);
            User userModel=new User();
            userModel.setId(user.getUsername());
            userModel.setFirstName(userAttributes.get(SrtConstant.FIRST_NAME));
            userModel.setLastName(userAttributes.get(SrtConstant.LAST_NAME));
            userModel.setEmail(userAttributes.get(SrtConstant.USER_EMAIL));
            userModel.setShortName(userAttributes.get(SrtConstant.STORE_NAME));
            userModel.setGroupName(userAttributes.get(SrtConstant.GROUPS));
            userModel.setStoreId(userAttributes.get(SrtConstant.STORE_ID));
            userModel.setStoreName(userAttributes.get(SrtConstant.STORE_NAME));
            log.info("userModel :", userModel);
        return ResponseEntity.ok().body(userModel);
    }
}
Spring Security SAML配置
@Override
protected void configure(final HttpSecurity http) throws Exception {
log.info("configure : "+"spHost :"+ spHost +" ,KeystoreFilePath : "+keyStoreFile +" ,KeyStoreAlias : "+keyStoreAlias+" ,metadatpath : "+metadataPath);
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.antMatchers(HttpMethod.GET, "/receive/**").permitAll()
.antMatchers(HttpMethod.POST, "/receive/**").permitAll()
.antMatchers(HttpMethod.PATCH, "/receive/**").permitAll()
.antMatchers(HttpMethod.DELETE, "/receive/**").permitAll()
.antMatchers(HttpMethod.PUT, "/user").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.userDetailsService(samlUserDetailsService)
.serviceProvider()
.protocol("https")
.hostname(spHost)
.basePath("/")
.keyStore()
.storeFilePath(keyStoreFile)
.keyPassword(keyStorePassword)
.keyname(keyStoreAlias)
.and()
.and()
.identityProvider()
.metadataFilePath(metadataPath);
}
CORS过滤器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    final HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", srtUiUrl);
    response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Credentials", "true");

    if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);
    } else {
        chain.doFilter(req, res);
    }
}
Same-Site配置
server:
  port: 8443
  servlet:
    session:
      cookie:
        same-site: none
        secure: true

内容的提问来源于stack exchange,提问作者Gaurav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 14:15:41