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


前后端分离架构中,前端调用后端提供的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 {
    
    
    @Override
    public 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;
    }
    
	@Bean
    public CorsFilter corsFilter() {
    
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }    
}

ResponseBodyAdvice

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

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

猜你喜欢

转载自blog.csdn.net/name_sakura/article/details/129951531