> 文章列表 > 解决前后端分离架构中跨域问题

解决前后端分离架构中跨域问题

解决前后端分离架构中跨域问题

文章目录

  • 1. 为什么产生跨域问题
    • (1)同源策略
    • (2)非跨域请求与跨域请求
  • 2.浏览器对请求的分类
    • (1)简单请求:浏览器先发送请求再判断是否跨域
    • (2)非简单请求:浏览器先发送预检命令(OPTIONS 请求方法),检查通过后才发送真正的数据请求
  • 3.解决跨域问题
    • (1)nginx中配置地址转发
    • (2)nginx中添加允许跨域请求头
    • (3)后端解决跨域问题
      • 通过配置文件跨域
      • 通过 CorsFilter 跨域
      • ResponseBodyAdvice

前后端分离架构中,前端调用后端提供的API接口来获取数据。由于浏览器的
同源策略要求当前请求与目标请求的域名、协议、端口都要相同,而前端服务与后端服务往往会被部署到不同的机器,不同端口上,因此会产生跨域问题。

1. 为什么产生跨域问题

(1)同源策略

同源策略是由 Netscape 提出的一个著名的安全策略,它是浏览器最核心也是最基本的安全功能,所有支持 JavaScript 的浏览器都会使用这个策略。在同源策略中,要求当前请求与目标请求的域名、协议、端口都要相同。 同源策略具体规则如下表:
解决前后端分离架构中跨域问题

(2)非跨域请求与跨域请求

非跨域请求,在请求头中会只包含请求的主机名。

解决前后端分离架构中跨域问题
跨域请求,在请求头中会既包含要请求的主机名还包括当前的源主机名

解决前后端分离架构中跨域问题

2.浏览器对请求的分类

HTTP1.1 协议中,请求方法分为 GETPOSTPUTDELETEHEADTRACEOPTIONSCONNECT 八种。浏览器根据请求方法和请求类型将请求划分为 简单请求非简单请求

(1)简单请求:浏览器先发送请求再判断是否跨域

  • 请求方法为 GETPOSTHEAD
  • Request Headers 中无自定义的请求头信息
  • Content-Type 为 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

(2)非简单请求:浏览器先发送预检命令(OPTIONS 请求方法),检查通过后才发送真正的数据请求

预检请求 Headers:

  • Access-Control-Request-Headers: content-type,x-token
    • 指明实际请求所携带的字段
  • Access-Control-Request-Method: POST
    • 指明实际请求所使用的 HTTP 方法

预检响应 Header:

  • Access-Control-Allow-Origin: http://localhost:8080
    • 指明允许访问的域。
  • Access-Control-Allow-Methods: POST
    • 指明允许的 HTTP 请求方法。
  • Access-Control-Allow-Headers: content-type, x-token
    • 指明允许携带的字段。
  • Access-Control-Max-Age: 3600
    • 指明该响应的有效时间,在有效时间内,浏览器无须为同一请求再次发起预检请求。浏览器检查预检响应信息,如果预检通过就发送实际请求。使用预检请求可以避免跨域请求对服务器的数据产生未预期的影响。

请求方法为 PUTDELETE 的 AJAX 请求、发送 JSON 格式的 AJAX 请求、带自定义头的 AJAX 请求都是非简单请求。

3.解决跨域问题

Nginx部署: 195.128.10.1:8080
前端服务部署: 195.128.10.1:9528
后端服务1部署: 195.128.10.2:8989
后端服务2部署: 195.128.10.2:8999
后端服务3部署: 195.128.10.2:9999

(1)nginx中配置地址转发

用户访问 Nginx 地址http://195.128.10.1:8080,前端访问的后端地址为http://195.128.10.1:8080/resource1 ,http://195.128.10.1:8080/resource2,http://195.128.10.1:8080/resource3。Nginx根据url转发。

注意:proxy_set_header 是 nginx 设置请求头给上游server服务器

server
{listen       8080;server_name localhost;location /js {proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://192.168.137.189:8081/; # 转发地址}location /resource1 {proxy_pass http://195.128.10.2:8989; # 转发地址proxy_read_timeout 600s;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $host:$se;}location /resource2 {proxy_pass http://195.128.10.2:8999; # 转发地址proxy_read_timeout 600s;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $host:$se;}location /resource3 {proxy_pass http://195.128.10.2:9999; # 转发地址proxy_read_timeout 600s;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $host:$se;}
}

(2)nginx中添加允许跨域请求头

用户访问前端地址http://195.128.10.1:9528,前端访问Nginx地址http://195.128.10.1:8080/resource1 ,http://195.128.10.1:8080/resource2,http://195.128.10.1:8080/resource3,Nginx根据URL转发。

注意:add_header是nginx设置响应头信息给浏览器

server {listen       8080;server_name  localhost;location  /resource1  {add_header Access-Control-Allow-Origin 'http://localhost:8080' always;add_header Access-Control-Allow-Headers '*';add_header Access-Control-Allow-Methods '*';add_header Access-Control-Allow-Credentials 'true';if ($request_method = 'OPTIONS') {return 204;}proxy_pass  http://195.128.10.2:8989;}location  /resource2  {add_header Access-Control-Allow-Origin 'http://localhost:8080' always;add_header Access-Control-Allow-Headers '*';add_header Access-Control-Allow-Methods '*';add_header Access-Control-Allow-Credentials 'true';if ($request_method = 'OPTIONS') {return 204;}proxy_pass  http://195.128.10.2:8999;}location  /resource3  {add_header Access-Control-Allow-Origin 'http://localhost:8080' always;add_header Access-Control-Allow-Headers '*';add_header Access-Control-Allow-Methods '*';add_header Access-Control-Allow-Credentials 'true';if ($request_method = 'OPTIONS') {return 204;}proxy_pass  http://195.128.10.2:9999;}
}

(3)后端解决跨域问题

https://blog.csdn.net/m0_71777195/article/details/126830773

通过配置文件跨域

创建一个新配置文件;添加 @Configuration 注解,实现 WebMvcConfigurer 接口;重写 addCorsMappings 方法,设置允许跨域的代码。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/").allowCredentials(true).allowedOriginPatterns("*").allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE").allowedHeaders("*").exposedHeaders("*");}
}

通过 CorsFilter 跨域

@Configuration
public class CorsConfig {private CorsConfiguration buildConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.setAllowCredentials(true);corsConfiguration.setMaxAge(3600L);return corsConfiguration;}@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/", buildConfig());return new CorsFilter(source);}    
}

ResponseBodyAdvice

通过重写 ResponseBodyAdvice 接口中的 beforeBodyWrite(返回之前重写)方法,可以对所有的接口进行跨域设置

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {/* 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)* 返回 true 表示重写*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/* 方法返回之前调用此方法*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {// 设置跨域response.getHeaders().set("Access-Control-Allow-Origin", "*");return body;}
}