前言

记录 Spring Cloud Gateway 整合 Spring SecurityOauth2 时跨越问题相关解决过程

项目架构

为了不直接暴露 API 及保护服务器,所有访问都需要经过网关,由网关转发请求到服务器及返回服务器的响应

springgateway 另一台服务器 springgateway跨域_sed

初遇跨域

跨域其实是很常见的问题,在 Spring 中可以简单的写个 @CrossOrigin 或者全局拦截器之类的解决掉,但在 Spring Cloud Gateway 中这行不通,写注解等方式在路由转发时还是会跨域

springgateway 另一台服务器 springgateway跨域_spring_02


官方文档 给出相关跨域配置(实测不好用)

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "https://docs.spring.io"
            allowedMethods:
            - GET

不过你可以轻易的通过Google搜索到如下代码

import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Bean
public CorsWebFilter corsFilter() {
	UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
	CorsConfiguration config = new CorsConfiguration();
	config.addAllowedOrigin("*");
	config.setAllowCredentials(true);
	config.addAllowedHeader("*");
	config.addAllowedMethod("*");
	source.registerCorsConfiguration("/**", config);

	return new CorsWebFilter(source);
}

简单的在启动类上或者其他什么地方加上后会发现跨域解决了


架构升级

在原系统中加个 Bean 就解决了跨域问题,现在为了进行 权限控制 我们需要给系统加上 Oauth2 认证

springgateway 另一台服务器 springgateway跨域_spring_03


当用户访问敏感资源时需要用户登录并授权(类似第三方登录)

springgateway 另一台服务器 springgateway跨域_spring_04


springgateway 另一台服务器 springgateway跨域_sed_05

这时候 Gateway 既是网关也是 Oauth2 中的 Client 角色,用户授予 Gateway 某些权限可以访问用户相关资源,Gateway 在获得授权后从 UAA 中获得Token 。用户请求到达 Gateway 后每次转发都会带上 TokenResource ServerToken 进行验证并作出响应。

Gateway 项目相关依赖如下:

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.12.RELEASE</version>
	<relativePath/>
</parent>


<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Greenwich.SR5</spring-cloud.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-oauth2-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-gateway</artifactId>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

再遇跨域

在引入 Oauth2Security 相关依赖后发现又跨域了

springgateway 另一台服务器 springgateway跨域_spring_06


可以发现 GET 请求正常,但 POST 等非简单请求遇到Preflight问题,这个问题的一般思路是拦截 OPTION请求直接返回 200 以便后续真正的请求顺利发起

其实这个问题是花时间最多的,期间写过 Gateway 的拦截器、Security 的拦截器、全局拦截器、Webflux 配置和全局配置等等,没一个能打的

然后考虑到是引入 Oauth2Security 相关依赖后才使原来的 Bean 失效,那可能是优先级的原因,请求在进入定义的 Filter 前就别拦截了。于是尝试提高 Bean 的优先级,但还是无果

最后在审查 Bean 代码时发现在创建 CorsWebFilter 时需要 ConfigurationSource 作为入参,那么拦截了请求的 Filter 会不会也需要相应的 ConfigurationSource 呢?于是将 ConfigurationSource 单独作为 Bean 创建,结果成功解决 Preflight 问题

@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
	UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
	CorsConfiguration config = new CorsConfiguration();
	config.addAllowedOrigin("*");
	config.setAllowCredentials(true);
	config.addAllowedHeader("*");
	config.addAllowedMethod("*");
	source.registerCorsConfiguration("/**", config);
	return source;
}

解决 Preflight 后又遇到 403 问题

springgateway 另一台服务器 springgateway跨域_spring_07


查看响应

springgateway 另一台服务器 springgateway跨域_sed_08


是CSRF相关,只要关掉就好了

import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
														ReactiveClientRegistrationRepository clientRegistrationRepository) {
	http.oauth2Login();
	http.authorizeExchange().anyExchange().authenticated();
	http.csrf().disable();

	return http.build();
}

至此,跨域问题完全解决