什么是跨域(CORS)?
CORS,全称:跨域资源共享(Cross-origin resource sharing),是一种基于HTTP协议的机制,允许服务器跨网域访问资源。
这里的网域指的是HTTP网域,比如https://1.1.1.1 和http://1.1.1.1 ,这就是2个网域,怎么判断的呢?众所周知,一个基本的网域是由<协议>://<域名/IP地址>[:<端口号>]组成,只要这三项中,有任意一项不一致,则都不是同一个网域。
跨域会造成什么问题?
如果在默认情况下,通过主机A(假设为https://1.1.1.1 )去访问主机B(假设为https://1.1.1.2 )的资源的时候,根据我们的判断,这是两个域。
在这种情况下,会产生一个HTTP错误:Cross-Origin Request Blocked: The Same Origin Policy disallows...
这种错误并不是我们写的代码哪里是错误的;其实,这是一种安全机制,是允许主机访问同源的策略,但同时拒绝其他主机的请求,即使浏览器拿到了数据,但是由于违反了这个安全策略,浏览器也不会把数据交给你,如图:
所谓的同源,就是<协议>://<域名/IP地址>[:<端口号>]三个部分全部一致,这就叫做同源。
这种问题在刚刚入门的开发者中也很常见,比如Web的前后端分离的项目,不管在开发还是部署环境中,都会难免遇到跨域问题。
解决方案
通过代码解决
通过代码解决,我们需要知道,在什么情况下才不会出现这种问题。
我们可以得知,CORS有几个配套的HTTP标头,常见的例如:
- Access-Control-Allow-Origin 允许指定源的访问
- Access-Control-Allow-Methods 允许指定方法的访问
- Access-Control-Allow-Headers 允许指定标头
- Access-Control-Allow-Credentials 允许跨域携带凭据
不推荐的原因:很多人会因为方便,将其设置为允许所有源访问,其实这不是安全的操作,故此不建议在部署环境中使用此方法。
我们可以通过以上标头解决这类问题:
在Spring Boot中的解决方法:
- 我们在Spring Boot的配置包(一般情况下在启动类的同级目录中)
- 新建一个名为 CorsConfigure 的类,并填入如下代码:
// 包名略
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class CorsConfigure implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许的路径
registry.addMapping("/**")
// 允许的源:*表示所有,也可以指定一个域,例如:http://localhost:8080
.allowedOriginPatterns("*")
// 允许的方法
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
// 是否允许跨域携带凭据
.allowCredentials(true)
// 最大存活时间
.maxAge(3600)
// 允许的标头
.allowedHeaders("*");
}
}
在Golang的Gin中的解决方法:
创建一个中间件即可:
import (
"github.com/gin-gonic/gin"
)
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
// 这里原来的代码是在配置文件中获取允许的源的数组,为了方便写博客,所以改成了直接声明的数组
allowOriginList := [4]string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082", "http://localhost:8083"}
// 判断allowOriginList的长度是不是等于1,且第0个元素是否为"*"
if len(allowOriginList) == 1 && allowOriginList[0] == "*" {
// 写入标头,值为*,表示允许所有源
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
} else {
// 遍历allowOriginList
for _, allowOrigin := range allowOriginList {
// 判断当前源是否为允许的源,如果不是,则直接跳过该步骤
if origin == allowOrigin {
// 如果是允许的源,就将其写入标头
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
结束循环
break
}
}
}
// 是否允许跨域携带凭据
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
// 允许的标头
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
// 允许的请求方法
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
// OPTIONS方法是一个特殊方法,用于请求预检,获取服务器是否允许该请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
// 继续下一个中间件
c.Next()
}
}
通过Nginx反向代理解决
适用于部署环境
用反向代理来解决,并不是设置标头,而是将2个不同的域代理成相同的域。
server {
# 监听80端口(由于这是我服务器上面的配置,所以没有监听80)
# listen 80;
# 监听443端口,并装载SSL
listen 443 ssl;
# 主机名称(你想要通过什么访问该网站,可以是域名也可以是IP地址)
server_name www.sgtu.org;
# 前端根路径
root /var/www/wordpress;
# 前端的主页文件
index index.html;
# SSL证书(未启用SSL则不需要)
ssl_certificate /etc/letsencrypt/live/shawngao.org/fullchain.pem;
# SSL证书私钥(未启用SSL则不需要)
ssl_certificate_key /etc/letsencrypt/live/shawngao.org/privkey.pem;
# 监听根路径
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# 监听请求的/api路径
location /api {
# 设置代理请求头:用于识别通过代理或负载均衡器方式连接到Web的客户端的原始IP地址和HTTP头字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 设置代理请求头:用于确定客户端发起的请求中使用的初始域名
proxy_set_header X-Forwarded-Host $http_host:$server_port;
# 设置代理请求头:用于确定客户端发起的请求中使用的代理服务器
proxy_set_header X-Forwarded-Server $host;
# 设置代理请求头:用于确定客户端真实IP
proxy_set_header X-Real-IP $remote_addr;
# 设置代理请求头:用于确定客户端发起的请求的协议
proxy_set_header X-Forwarded-Proto $scheme;
# 目标地址:http://【后端IP地址】:【后端端口】
# 注意:后面不要加斜线“/”
proxy_pass http://172.0.0.1:81;
# 上传的最大文件尺寸
client_max_body_size 20m;
}
}
通过以上的Nginx的配置,前端(假设为:https://www.sgtu.org )直接请求后端(https://www.sgtu.org/api )即可解决跨域问题。
Comments | NOTHING