浅谈跨域问题以及如何使用Nginx解决跨域问题

跨域问题

跨域问题经常发生在前后端分离开发模式中,指的是浏览器不允许非同源的请求调用,是浏览器出于安全考虑的一种策略.
两个网址之间只要协议, 域名, 端口, 子域名其中一个不同, 就会被视为非同源.

举个栗子

前端用html + js 代码做一个简单的接口调用

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body>
<h2>Hello World!</h2>
<script type="text/javascript">

    var baseUrl = "http://localhost:8888/test";

    function getFunc(){
        var request = new XMLHttpRequest();
        request.open("GET", baseUrl + "/get")
        request.send();
        request.onreadystatechange = function(){
            if(request.status==200 && request.readyState == 4){
                console.log(request.responseText)
            }
        }
    }

    function postFunc(){
        var request = new XMLHttpRequest();
        request.open("POST", baseUrl + "/post")
        request.send();
        request.onreadystatechange = function(){
            if(request.status==200 && request.readyState == 4){
                console.log(request.responseText)
            }
        }
    }

    function putFunc(){
        var request = new XMLHttpRequest();
        request.open("PUT", baseUrl + "/put")
        request.send();
        request.onreadystatechange = function(){
            if(request.status==200 && request.readyState == 4){
                console.log(request.responseText)
            }
        }
    }

    function deleteFunc(){
        var request = new XMLHttpRequest();
        request.open("DELETE", baseUrl + "/delete")
        request.send();
        request.onreadystatechange = function(){
            if(request.status==200 && request.readyState == 4){
                console.log(request.responseText)
            }
        }
    }
    
</script>
</body>
    <input type="button" value="get方法" onclick="getFunc()">
    <input type="button" value="post方法" onclick="postFunc()">
    <input type="button" value="put方法" onclick="putFunc()">
    <input type="button" value="delete方法" onclick="deleteFunc()">
</html>

后端用java写几个简单的接口

@RestController
@RequestMapping("/test")
public class Test {

    @GetMapping("/get")
    public String getMethod(){
        System.out.println("执行了一次get操作");
        return "get method";
    }

    @PostMapping("/post")
    public String postMethod(){
        System.out.println("执行了一次post操作");
        return "post method";
    }

    @PutMapping("/put")
    public String putMethod(){
        System.out.println("执行了一次put操作");
        return "put method";
    }

    @DeleteMapping("/delete")
    public String deleteMethod(){
        System.out.println("执行了一次delete操作");
        return "delete method";
    }
}

此时无论以何种请求方法调用后台接口都会触发跨域问题
跨域问题触发时浏览器控制台的报错信息

Nginx解决跨域问题

Nginx是一个高性能的反向代理服务器,它除了可以做反向代理外,对于分布式的系统还可以实现负载均衡功能.本文主要讲如何通过Nginx反向代理解决跨域问题.
先贴上我的nginx.conf配置文件

user  root;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location /test/ {
            # 允许跨域请求的来源, *表示所有
            add_header Access-Control-Allow-Origin *;
            # 允许跨域请求的方法类型
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
            # OPTIONS请求返回的头部信息中Access-Control-Allow-Headers字段的值
            add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
            # 请求转发路径
            proxy_pass http://192.168.1.101:8888/test/;

            # if ($request_method = 'OPTIONS') {
            #         add_header Access-Control-Allow-Origin *;
            #         add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,PATCH,OPTIONS;
            #         return 204;
            # }
        }
    }
}

配置文件中
Access-Control-Allow-Origin * 代表允许所有来源的跨域请求调用
Access-Control-Allow-Methods 表示允许的方法类型
Access-Control-Allow-Headers 表示OPTIONS方法返回的Access-Control-Allow-Headers字段(这个后边具体说)
proxy_pass http://192.168.1.101:8888/test/; 表示转发的地址, 由于我nginx是跑在docker中的,因此需要用本机的IP地址, 如果nginx是直接跑在机器上的用localhost或127.0.0.1即可.

用这个配置文件启动nginx之后, 将html代码中的baseUrl改为 http://localhost/test 之后再次尝试调用接口.

var baseUrl = "http://localhost/test";

在这里插入图片描述
根据控制台打印结果可以发现, get跟post的请求可以正常调用, 但是put跟delete请求仍然报跨域异常(原因下边说).

接着讲nginx配置文件中注释部分代码取消注释, 重启nginx, 发现put跟delete类型的方法也可以正常调用

	if ($request_method = 'OPTIONS') {
                  add_header Access-Control-Allow-Origin *;
                  add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,PATCH,OPTIONS;
                  return 204;
     }

在这里插入图片描述

什么时候发送OPTIONS方法

预检请求

HTTP请求可以分为简单HTTP请求需预检HTTP请求(复杂HTTP请求).
满足以下条件的可以视为简单HTTP请求:
使用的方法为GET, POST, HEAD之一且Content-Type的值仅为下列之一:test-plain, multipart/form-data, application/x-www-form-urlencoded
其余的全视为需预检HTTP请求.

OPTIONS方法

在发起需预检HTTP请求时,总共会发出两个请求,首先发送OPTIONS方法, 如果OPTIONS方法的返回值符合条件,才可以发送真实的请求
例如前边的例子, 在我们发送delete或者put类型请求时,观察浏览器控制台可以发现总共发送了两个请求
在这里插入图片描述
其中Type为preflight类型的请求中, 调用的方法类型即为OPTIONS
在这里插入图片描述
查看这个请求的具体返回值我们可以发现response的头部中有Access-Control-Allow-OriginAccess-Control-Allow-Methods两个字段,他们的值就是我们在Nginx配置文件中设置的值

if ($request_method = 'OPTIONS') {
                    add_header Access-Control-Allow-Origin *;
                    add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,PATCH,OPTIONS;
                    return 204;
            }

上边这段代码的意思就是当请求方法类型为OPTIONS时,将允许的来源和方法设置进response头部中,并且将状态码设置为204(成功且不刷新页面).
如果将这段代码注释掉后重启nginx会发现preflight请求失败
在这里插入图片描述
但是这里有个疑惑就是为什么需要针对OPTIONS类型的方法去做特殊处理,不是所有的请求都会经过nginx并且将Access-Control-Allow-OriginAccess-Control-Allow-Methods两个字段设置好吗.希望有懂的大佬可以留言解惑下.

至此用Nginx解决跨域问题的介绍完毕, 如果有什么错误的地方希望各位指出, 谢谢!

参考文章

【转】前端 | 浅谈预检请求
Nginx配置跨域请求 Access-Control-Allow-Origin *

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值