跨域问题
跨域问题经常发生在前后端分离开发模式中,指的是浏览器不允许非同源的请求调用,是浏览器出于安全考虑的一种策略.
两个网址之间只要协议, 域名, 端口, 子域名其中一个不同, 就会被视为非同源.
举个栗子
前端用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-Origin和Access-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-Origin和Access-Control-Allow-Methods两个字段设置好吗.希望有懂的大佬可以留言解惑下.
至此用Nginx解决跨域问题的介绍完毕, 如果有什么错误的地方希望各位指出, 谢谢!