Spring Cloud Gateway作为OAuth2 Client

目录

    • 1. 引言
    • 2. SCG作为OAuth2 Client的整体架构
      • 2.1 SPA作为前端应用接入SCG Client
      • 2.2 SCG Client代理Web Template/Ajax应用
    • 3. SCG集成OAuth2 Client遇到的问题
      • 3.1 分布式session
      • 3.2 refresh_token过期导致SCG返回Http Status 500
      • 3.3 设置SCG Client登录入口(Ajax请求401,OPTIONS请求200,其他默认OAuth2 302 Redirect登录)
      • 3.4 扩展支持redirect_uri参数
      • 3.5 OIDC登出 - 刷新token后同步更新SecurityContext中id_token
      • 3.6 跨域Cookie传输
    • 4. 示例代码

1. 引言

Spring Cloud Gateway(后文简称SCG)作为Spring生态新一代的应用网关,其作为整个应用集群的守门人,其除了可以负责路由转发,还可以将本来是属于各个服务的鉴权、限流、熔断等工作统一前置到应用网关统一进行配置管理,从而也减轻了后端接入服务的工作量,使后端服务更专注于核心业务逻辑的开发。同样SCG应用网关也可以将接入OAuth2的工作统一前置到网关中,SCG在OAuth2协议栈中:

  • 既可以充当Resource Server的角色
  • 也可以充当Client的角色。

如果接入OAuth2协议的Client端,如SPA(浏览器端)、Native App(移动app、桌面应用)具有独立完成OAuth2认证(如授权码模式)的能力(语言框架支持、开发人员可以配合修改代码),还是首选SCG仅作为Resource Server,即:

  • SCG仅对access_token进行认证,认证通过才会转发请求到后端服务
  • 后端服务无需关心OAuth2
  • SCG无状态(无需session维护用户登录态、用户信息等)
  • 由Client端自行负责OAuth2授权流程
    • 跳转OAuth2 authorization_endpoint(即跳转到Auth Server登录页)
    • 携带授权码code调用OAuth2 token_endpoint获取access_token、id_token、refresh_token等
    • 维护用户登录态、token及用户信息
    • 携带bearer access_token调用OAuth2 userinfo_endpiont获取用户信息
    • 携带bearer access_token调用SCG(由SCG代理后端服务)

以上SCG作为Resource Server是比较理想的状态(职责划分清晰、SCG无状态),但需要Client端承担一定接入OAuth2的工作量,所以实际向各部门推行此套架构的时候,由于Client端接入OAuth2的门槛最终还是给落地带来了一些阻力,所以为了减轻Client端的接入复杂程度,也就需要SCG承担更多的工作,因此也就引出了本文的核心:SCG作为OAuth2 Client,即:

  • 让SCG来承担与OAuth2授权交互的工作
  • SPA、Web Ajax应用以更简单的方式来接入认证(隔离OAuth2的接入复杂性)
  • SCG作为OAuth2 Client仅适用于浏览器应用(支持cookie)

2. SCG作为OAuth2 Client的整体架构

SCG作为OAuth2 Client的整体架构如下图:


本文讲解的OAuth2接入皆是基于Spring Security OAuth2相关生态,
而Spring Security OAuth2也分为:

  • Servlet Applications - 传统Spring Mvc开发(基于Tomcat、Undertow等容器)
  • Reactive Applications - 基于响应式编程(Web Flux)

由于SCG建立在WebFlux上,所以相关集成说明参见:
https://docs.spring.io/spring-security/reference/reactive/oauth2/index.html

Spring Cloud Gateway作为OAuth2 Client_第1张图片
如上架构设计,即SCG作为OAuth2 Client的首要前提:
仅支持浏览器端应用(支持Cookie Storage、支持302重定向)
如SPA(VUE)、Web Template/Ajax应用等运行在浏览器中的应用,而像Native App(移动app、桌面应用)等无法直接使用Cookie Storage的应用形态,还是首选推荐SCG作为Resource Server的架构,也就是说SCG作为Resource Server的适用性更广(SPA、Native App、Web应用),而SCG作为OAuth2 Client仅适用于浏览器端应用(SPA、Web应用)。

在上述架构中,可以将应用分为3类:

  • 第1类:前端应用 - 运行在浏览器端,向SCG发送请求

    • SPA - 独立运行的SPA应用(如Vue),可与SCG部署在不同域名下
      • 通过Ajax请求SCG后端服务
      • 与SCG跨域
    • Web Template/Ajax - 通过请求后端Web应用渲染Template页面(如Thymeleaf、JSP等)
      • 请求html页面
      • html页面中亦存在Ajax请求,即Ajax请求SCG后端服务
      • html页面和页面中的Ajax请求可隶属于同一Web应用,亦可跨Web应用
      • 通过SCG请求,即与SCG同域名
  • 第2类:后端应用 - 由SCG代理的后端应用

    • Resource Server - 后端API服务,不包含页面
      • 可接入OAuth2 Resource Server,对请求中的bearer access_token(由SCG透传到后端服务)进行认证
    • Web Template/Ajax - 传统web应用,同时提供html页面(如Thymeleaf、JSP等)和Api服务(支持Ajax调用)
  • 第3类:应用网关SCG Client - 负责接入OAuth2授权、维护用户Session及Cookie、透传access_token到后端服务

    • 若浏览器向SCG发起未认证的请求
      • html页面请求(text/html) - 302重定向到SCG Client授权端点
      • Ajax请求(xhr - application/json) - 返回Http 401 Unauthorized
    • 代理OAuth2授权 - /oauth2/authorization/{clientRegId} - 触发SCG Client的OAuth2登录流程
    • 处理OAuth2授权回调 - /login/oauth2/code/{clientRegId}?code=…state=…
    • 获取token、刷新token、透传token到后端服务
    • 维护用户Web Session

前端应用SCG Client间通过Session Cookie来保持用户状态,即由SCG Client完成OAuth2授权流程后,首先将授权信息(access_token、refresh_token、id_token、userInfo)写入到SCG端Session存储,然后再向浏览器端写Session Cookie,之后前端应用(同域、跨域)向SCG发送请求时,会携带SCG域名下的Session Cookie,SCG对Session Cookie认证通过后,可以将access_token透传到后端应用


2.1 SPA作为前端应用接入SCG Client

关于SPA请求SCG Client的完整认证过程参见如下顺序图: Spring Cloud Gateway作为OAuth2 Client_第2张图片
如上图,SPA接入SCG Client,仅需统一处理Ajax调用返回的Http status 401 Unauthorized,
在收到401后,可统一重定向到SCG Client的自身的OAuth2 Authorization Endpoint:
https://scg-client/oauth2/authorization/{clientRegId}?redirect_uri=https://spa
同时可附加redirect_uri参数,此参数用于设置SPA自身的地址,
SCG Client会在完成OAuth2授权流程后再重定向回此参数指定的uri地址,即重定向回SPA,
此redirect_uri为扩展参数,后文有讲解,并非Spring Security OAuth2 Client原生支持的。

如果后端Api服务不想被任意访问,则SCG可为后端route配置TokenRelay过滤器,即将bearer access_token透传到后端的API服务,
而后端Api服务可以接入Spring Security OAuth2 Resource Server模块来完成对access_token的校验。
如果后端Api服务在部署在内网,不可被公开访问,亦可不开启TokenRelay过滤器,因为SCG Client已经完成过OAuth2登录,即已经认证过用户,所以后端Api服务可无需再对用户进行认证,若有获取用户信息的需要,可参见下面讲解中提到的自定义UserInfoRelay过滤器,将SCG Web Session的用户信息透传到后端Api服务即可。


2.2 SCG Client代理Web Template/Ajax应用

Web Template/Ajax同时包含前端Html页面和后端Api服务,
关于SCG Client代理Web Template/Ajax服务的完整认证过程参见如下顺序图:
Spring Cloud Gateway作为OAuth2 Client_第3张图片
如上图,Web Template/Ajax接入SCG Client,
对于Html页面请求,SCG Client通过SaveRequest、302重定向机制自动完成OAuth2授权流程,
而对于Html页面中的发送后端服务的Ajax请求,仅需统一处理Ajax调用返回的Http status 401 Unauthorized,
在收到401后,可统一重定向到任一Html页面请求(如Homepage),请求Html页面即可由SCG Client自动完成OAuth2授权流程,

若后端服务有获取用户信息的需求,可自定义UserInfoRelay过滤器(实现逻辑参考TokenReplay),
SCG仅需将Web Session中的SPRING_SECURITY_CONTEXT用户信息透传到到后端服务,
而后端服务可根据需要读取x-user-info请求头获取用户信息(无需接入OAuth2 ResourceServer)。


3. SCG集成OAuth2 Client遇到的问题

3.1 分布式session

由于SCG OAuth2 Client与浏览器端前端应用通过Session Cookie保存用户会话及登录态,所以为了保证SCG Client支持水平扩展,所以SCG需要使用分布式Session,如借助Redis存储统一管理Session,而不使用内存Session存储,避免前端请求路由到不同SCG部署实例后无法通过Cookie找到对应的内存Session。
在Spring生态中可以集成使用Spring Session,而Spring Session又分为HttpSession和WebSession等,而SCG(基于WebFlux)则需集成WebSession。

Spring Cloud Gateway作为OAuth2 Client_第4张图片
Spring Session支持的后端存储如下图:
Spring Cloud Gateway作为OAuth2 Client_第5张图片
实际集成时使用的Redis数据存储,具体集成Spring Session Redis Reactive配置application-session.yml如下:

spring:
  # Redis配置
  redis:
    host: localhost
    port: 6379
    database: 0
    #password:
    client-type: lettuce
    lettuce.pool:
      enabled: true
      max-active: 8
      max-idle: 8
      min-idle: 0
      max-wait: -1ms
      #time-between-eviction-runs:
  # Spring Session配置
  session:
    # session超时时间(默认30分钟,即1800s)
    timeout: 600s
    # 启动SpringSession Redis
    store-type: redis
    # SpringSession - Redis相关配置
    redis:
      # 清理失效session的cron表达式(默认每分钟)
      cleanup-cron: 0 * * * * *
      # session存储namespace(key前缀)
      # 默认spring:session
      namespace: scg:client
      # 保存模式,即如何保存Session属性(默认on_set_attribute)
      # on_set_attribute: 仅对调用过Session.setAttribute(String, Object)的属性进行保存
      # on_get_attribute: 仅对调用过Session.setAttribute(String, Object)和Session.getAttribute(String)的属性进行保存
      # always: 全量保存Session中的attributes
      save-mode: on_set_attribute
      # 刷新模式,即何时保存Session(默认on_save)
      # on_save: 仅调用SessionRepository.save(Session)时保存Session,对应提交Response时
      # immediate: 立即保存Session,对应SessionRepository#createSession()或者Session.setAttribute(String. Object)
      flush-mode: on_save
      # 配置活动(默认启用Keyspace events)
      configure-action: notify_keyspace_events
server:
  # webflux session cookie设置
  reactive:
    session:
      cookie:
        # 设置session Cookie名称
        name: SCG_CLIENT_SESSION_ID
        # http-only(禁止JS读取cookie)
        http-only: true
        # cookie过期时长(单位:秒)
        # 设置同Spring Session timeout
        # 默认不设置 空或者<0,即等同于session时效,即关闭浏览器时自动失效
        #max-age: ${spring.session.timeout}
        # =============== 支持Cookie Https且支持CORS ==================
        # cookie secure使用https
        secure: true
        # cookie sameSite设置
        same-site: none

实际开发时,除了上面提到的SCG Client需要支持分布式Session,而AuthServer(基于Spring Authorization Server实现 )也需要支持分布式Session,而AuthServevr基于Servlet容器实现,则需要集成HttpSession,基于HttpSession的配置文件如下:

# 之前的Spring.session配置与上面的配置一样,故省略
# 需要注意的是Cookie设置的前缀是不同的,
# WebSession Cookie配置前缀:server.reactive.session.cookie
# HttpSession Cookie配置前缀:server.servlet.session.cookie
server:
  servlet:
    # 设置session cookie相关
    session:
      cookie:
        # 设置session Cookie名称
        name: AUTH_SERVER_SESSION_ID
        # http-only(禁止JS读取cookie)
        http-only: true
        # cookie过期时长(单位:秒)
        # 设置同Spring Session timeout
        # max-age: ${spring.session.timeout}
        # 默认不设置 空或者<0,即等同于session时效,即关闭浏览器时自动失效

3.2 refresh_token过期导致SCG返回Http Status 500

当前SCG Client检测到(SCG接收到请求时通过TokenRelayGatewayFilterFactory检测)当前用户的access_token还剩1分钟(默认值,可通过设置修改)就过期时,会触发SCG Client端执行Refresh Token授权流程,而此时若SCG Client端存储的refresh_token已经过期时,会导致AuthServer端返回Http status 400 invalid_grant,进而导致SCG返回Http status 500,具体代码实现参见RefreshTokenReactiveOAuth2AuthorizedClientProvider转换OAuth2AuthorizationException为ClientAuthorizationException导致返回
500,此处将ClientAuthorizationException转换为CredentialsExpiredException extends AuthenticationException,可以触发登录(避免返回500),具体SCG Client登录入口实现可参见后续讲解。
Spring Cloud Gateway作为OAuth2 Client_第6张图片

注:
具体的解决思路参见:https://github.com/spring-projects/spring-security/issues/11015
Spring Cloud Gateway作为OAuth2 Client_第7张图片
而GitHub上给出的是将ClientAuthorizationException转换为ClientAuthorizationRequiredException,
会直接302重定向到OAuth2 authorization_endpoint,而我在实际扩展时需要根据请求类型(Html、XHR)决定是重定向、还是返回401,所以最终转换为了CredentialsExpiredException extends AuthenticationException,触发进入SCG Client登录入口,而不是直接重定向到OAuth2 authorization_endpoint。同时由SCG域名跳转到AuthServer域名下authorization_endpoint还有跨域问题。

3.3 设置SCG Client登录入口(Ajax请求401,OPTIONS请求200,其他默认OAuth2 302 Redirect登录)

实际测试时发现Spring Security Reactive的登录入口不是很清晰,所以最后重写了登录入口规则,
即若发现用户未登录,则:

  • Ajax请求:返回Http Status 401
  • CORS Options Preflight请求:返回Http Status 200
  • 其他请求(text/html):Http Status 302 重定向到SCG Client登录端点/oauth2/authorization/{clientRegId}

具体代码配置如下:
Spring Cloud Gateway作为OAuth2 Client_第8张图片

/**
 * 自定义登录端点 - Ajax请求401,OPTIONS请求200,其他默认OAuth2 302 Redirect登录
 */
private ServerAuthenticationEntryPoint buildAjax401AndHtml302AuthEntryPoint() {
    //请求头accept为application/json且忽略*/*
    MediaTypeServerWebExchangeMatcher applicationJsonMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.APPLICATION_JSON);
    applicationJsonMatcher.setIgnoredMediaTypes(Stream.of(MediaType.ALL).collect(Collectors.toSet()));
    List<DelegatingServerAuthenticationEntryPoint.DelegateEntry> delegateEntryList = Arrays.asList(
            //请求头accept为application/json -> 返回401
            new DelegatingServerAuthenticationEntryPoint.DelegateEntry(
                    applicationJsonMatcher,
                    new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)),
            //请求头X-Requested-With为XMLHttpRequest -> 返回401
            new DelegatingServerAuthenticationEntryPoint.DelegateEntry(new ServerWebExchangeMatcher() {
                @Override
                public Mono<MatchResult> matches(ServerWebExchange exchange) {
                    String xRequestedWith = exchange.getRequest().getHeaders().getFirst("X-Requested-With");
                    Boolean match = StringUtils.hasText(xRequestedWith) && xRequestedWith.equals("XMLHttpRequest");
                    return match ? MatchResult.match() : MatchResult.notMatch();
                }
            }, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)),
            //跨域OPTIONS请求返回200(解决浏览器报错)
            new DelegatingServerAuthenticationEntryPoint.DelegateEntry(new ServerWebExchangeMatcher() {
                @Override
                public Mono<MatchResult> matches(ServerWebExchange exchange) {
                    HttpMethod method = exchange.getRequest().getMethod();
                    Boolean match = HttpMethod.OPTIONS.equals(method);
                    return match ? MatchResult.match() : MatchResult.notMatch();
                }
            }, new HttpStatusServerEntryPoint(HttpStatus.OK))
    );

    DelegatingServerAuthenticationEntryPoint nonAjaxLoginEntryPoint = new DelegatingServerAuthenticationEntryPoint(delegateEntryList);
    //默认登录入口即为OAuth2重定向登录端点
    nonAjaxLoginEntryPoint.setDefaultEntryPoint(new RedirectServerAuthenticationEntryPoint(this.oauth2LoginEndpoint));
    return nonAjaxLoginEntryPoint;
}

3.4 扩展支持redirect_uri参数

为了支持SPA登录跳转(可参见前文SPA作为前端应用接入SCG Client的顺序图),
Spring Cloud Gateway作为OAuth2 Client_第9张图片
即SPA请求SCG OAuth2 Client登录端点/oauth2/authorization/{clientRegId},同时携带redirec_uri参数,SCG会缓存此redirect_uri,在SCG Client认证完成后再由SCG重定向回redirect_url所指向的SPA。设计是如此,但是Spring Security OAuth2 Client是不支持此参数的,所以就需要我们自己扩展实现。

找到如下切入点,即扩展实现SCG OAuth2 Client登录端点/oauth2/authorization/{clientRegId}的解析器:
Spring Cloud Gateway作为OAuth2 Client_第10张图片

具体实现就是扩展原DefaultServerOAuth2AuthorizationRequestResolver实现,提取redirect_uri参数,并模仿WebSessionServerRequestCache保存此redirect_uri到Session中,后续认证完成后Security框架就会自动重定向到此Session中的redirect_uri。

import com.luo.demo.scg.client.config.Oauth2ClientSecurityConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;

/**
 * OAuth2 Client Authorization Endpoint /oauth2/authoriztion/{clientRegId} 
* 请求解析器扩展实现 - 支持提取query参数redirect_uri,用作后续OAuth2认证完成后SCG重定向到该指定redirect_uri。
* 适用场景:SPA -> SCG -> SCG返回401 -> SPA重定向到/oauth2/authoriztion/{clientRegId}?redirect_uri=http://spa -> SCG完成OAuth2认证后再重定向回http://spa * * @author luohq * @date 2022-06-13 * @see DefaultServerOAuth2AuthorizationRequestResolver * @see WebSessionServerRequestCache * @see Oauth2ClientSecurityConfig */
public class SaveRequestServerOAuth2AuthorizationRequestResolver extends DefaultServerOAuth2AuthorizationRequestResolver { private static final Log logger = LogFactory.getLog(SaveRequestServerOAuth2AuthorizationRequestResolver.class); /** * redirect uri参数名称 */ private static final String PARAM_REDIRECT_URI = "redirect_uri"; /** * WebSession对应的saveRequest属性名 * 完全沿用(兼容)WebSessionServerRequestCache定义 */ private static final String DEFAULT_SAVED_REQUEST_ATTR = "SPRING_SECURITY_SAVED_REQUEST"; private String sessionAttrName = DEFAULT_SAVED_REQUEST_ATTR; /** * Creates a new instance * * @param clientRegistrationRepository the repository to resolve the * {@link ClientRegistration} */ public SaveRequestServerOAuth2AuthorizationRequestResolver( ReactiveClientRegistrationRepository clientRegistrationRepository) { super(clientRegistrationRepository); } @Override public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange) { return super.resolve(exchange) .doOnNext(oAuth2AuthorizationRequest -> { //获取query参数redirect_uri Optional.ofNullable(exchange.getRequest()) .map(ServerHttpRequest::getQueryParams) .map(queryParams -> queryParams.get(PARAM_REDIRECT_URI)) .filter(redirectUris -> !CollectionUtils.isEmpty(redirectUris)) .map(redirectUris -> redirectUris.get(0)) .ifPresent(redirectUri -> { //若redirect_uri非空,则覆盖Session中的SPRING_SECURITY_SAVED_REQUEST为redirect_uri //即后续认证成功后可重定向回SPA指定页面 exchange.getSession().subscribe(webSession -> { webSession.getAttributes().put(this.sessionAttrName, redirectUri); logger.debug(LogMessage.format("SCG OAuth2 authorization endpoint queryParam redirect_uri added to WebSession: '%s'", redirectUri)); }); }); }); } }

3.5 OIDC登出 - 刷新token后同步更新SecurityContext中id_token

SCG Client在集成OidcClientInitiatedServerLogoutSuccessHandler实现OIDC登出时,需要提取SecurityContext中OIDC用户信息中的id_token,

GET end_session_point?id_token_hint={id_token_issued_to_client}&post_logout_redirect_uri={post_logout_redirect_uri}

实际测试发现若执行过Refresh Token流程,虽然SCG Client端存储(OAuth2AuthorizedClient)的access_token和refresh_token都已被动态更新,但是SecurityContext中OIDC用户信息中的id_token还是最初始登录时获取的值,在执行Refresh Token后没有被同步更新,进而导致执行OIDC登出时携带的id_token不是最新的,导致AuthServer返回异常(AuthServer扩展实现返回Http Status 400),所以为了解决这个问题就需要在SCG Client刷新Token后同步更新SecurityContext中OIDC用户信息中的id_token。

SCG Client刷新Token的具体调用链如下:

  • TokenRelayGatewayFilterFactory.apply.authorizedClient
    • ReactiveOAuth2AuthorizedClientManager.authorize(OAuth2AuthorizeRequest)
      • DefaultReactiveOAuth2AuthorizedClientManager.authorize(OAuth2AuthorizeRequest)
        • authorize(OAuth2AuthorizationContext, Authentication, ServerWebExchange)
          • ReactiveOAuth2AuthorizedClientProvider.authorize(OAuth2AuthorizationContext)
            • RefreshTokenReactiveOAuth2AuthorizedClientProvider.authorize(OAuth2AuthorizationContext)

最终定位到RefreshTokenReactiveOAuth2AuthorizedClientProvider,扩展实现如下:
Spring Cloud Gateway作为OAuth2 Client_第11张图片
Spring Cloud Gateway作为OAuth2 Client_第12张图片

3.6 跨域Cookie传输

Web Template/Ajax应用是通过SCG代理,所以请求Web Template/Ajax应用(Html页面、Ajax请求)也是直接请求SCG,然后由SCG路由到对应的Web Template/Ajax应用,即Web Template/Ajax应用是和SCG享有共同的域名的,如:

请求html页面:http://scg-client/webAjax/page
Html页面发送Ajax请求:http://scg-client/webAjax/svc

所以Web Template/Ajax应用和SCG不存在跨域的问题。


SPA可以部署到Nginx中,然后由Nginx路由到SCG Client,如:

请求SPA:http://nginx/spa
SPA向SCG网关发送Ajax请求:http://nginx/scg/resource1

此时SPA和SCG也不存在跨域问题。


而SPA部署在不同域名下(与SCG域名不同),又由于前端应用SPA和SCG间通过Session Cookie保持用户会话及登录态,此时就需要考虑跨域Session Cookie传输的问题了。如SCG Client认证完成后会向浏览器写SCG域名下(Domain=scg-client)的Session Cookie:

Set-Cookie: SCG_CLIENT_SESSION_ID=5f59d21a-31b0-4ab7-b150-59c65a17d5e6; Domain=scg-client,Path=/; HttpOnly;

而SPA部署在不同于SCG的域名下:

http://spa

此时由SPA向SCG发送Ajax请求,即存在跨域及跨域Session Cookie传输的问题,即SPA向SCG发送Ajax请求时默认是无法携带SCG域名下下的Cookie的,导致SCG检测不到Cookie而请求认证失败。若想实现跨域(SPA携带SCG Cookie)Session Cookie的传输,最终解决方案如下:

1)SCG Session Cookie支持Https及跨域传输:

Set-Cookie: SCG_CLIENT_SESSION_ID=5f59d21a-31b0-4ab7-b150-59c65a17d5e6; Path=/; Secure; HttpOnly; SameSite=None

注: 如此也即要求SPA和SCG均使用HTTPS协议

2)SCG(服务端)支持SPA域名及Cookie跨域:

注:setAllowCredentials为true时,allowedOrigin不能为*,即需明确设置allowOrigin对应域名

Spring Cloud Gateway作为OAuth2 Client_第13张图片

3)SPA(客户端)Ajax设置withCredentials=true(即支持跨域Cookie传输):
Spring Cloud Gateway作为OAuth2 Client_第14张图片

4. 示例代码

示例代码参见:https://gitee.com/luoex/oauth2-auth-server-oidc/tree/main/scg-client-prod-sample
Spring Cloud Gateway作为OAuth2 Client_第15张图片


参考:

Spring Session
https://spring.io/projects/spring-session
https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.spring-session

SCG & Spring Security OAuth2
https://github.com/spring-projects/spring-security/issues/11015

CORS Cookie
https://geekflare.com/enable-cors-httponly-cookie-secure-token/

Reactor
https://stackoverflow.com/questions/51315378/reactivesecuritycontextholder-getcontext-is-empty-but-authenticationprincipal
https://www.tabnine.com/code/java/classes/org.springframework.security.core.context.ReactiveSecurityContextHolder
https://www.tabnine.com/code/java/methods/reactor.util.context.Context/get

你可能感兴趣的:(架构设计,springcloud,#,OAuth2.0,&,OIDC,1.0,gateway,spring,security,oauth2,client,cors,cookie)