跨域场景下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
相关产品推荐
相关产品推荐

