跨域问题的产生和解决
1. 为什么会产生跨域
前后端分离模式下,客户端请求前端服务器获取视图资源,然后客户端自行向后端服务器获取数据资源,前端服务器的协议、IP和端口和后端服务器很可能是不一样的、这样就产生了跨域。
这主要是因为浏览器的同源策略导致的,同源策略要求网页资源(如 JavaScript、CSS、图片等)只能与来源相同的资源进行交互,即只能与相同域名、相同协议和相同端口的资源进行通信。例如,一个网页加载自 https://www.example.com 域名下的资源,就只能与同一域名下的其他资源进行交互,无法直接访问其他域名的资源。
同源策略主要限制了以下几种行为:
- DOM 访问限制:不同源的页面无法通过 JavaScript 等方式直接访问对方的 DOM 元素,即无法获取或修改对方页面的内容。
- Cookie、LocalStorage 和 IndexDB 限制:不同源的页面无法读取对方设置的 Cookie、LocalStorage 和 IndexDB 存储。
- AJAX 请求限制:XMLHttpRequest、Fetch 等网络请求在跨域时受到限制,通常无法发送跨域请求。
请求如果跨域的话不是请求无法发出,请求可正常发出,而且服务端能收到请求并正常返回结果,但是被浏览器拦截了。同源策略的存在有效地防止了跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等安全威胁。如果需要在不同源之间进行数据交互,可以通过服务器端的代理或使用 CORS(跨源资源共享)等技术来实现。
2. 浏览器拦截机制
2.1 简单请求
定义:请求方法是GET、HEAD、POST;
条件:HTTP头信息不超出Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width;
Content-Type:仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain;
拦截机制:简单跨域请求浏览器会自动带上Origin头部,用于标识请求的源(origin)。如果服务器响应头Access-Control-Allow-Origin,值包含请求的源,浏览器会允许跨域请求。否则,浏览器会拦截该请求的返回值。
2.2. 复杂请求
定义:请求方法是PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH;
条件:HTTP头信息超出简单请求的范围;
Content-Type:不属于application/x-www-form-urlencoded、multipart/form-data、text/plain;
拦截机制:复杂跨域请求浏览器会先发送一个OPTIONS预检请求(preflight request)到服务器,询问是否允许跨域请求。如果服务器允许,预检通过后才会发送真正请求并在服务端被执行。
3. 后端CORS解决方案
CORS (Cross-Origin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理。跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
@RestController
@CrossOrigin(origins = "http://example.com")
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello world";
}
}
配置方式:
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 配置跨域支持
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //允许的路径,即我们那些接口可以跨域
.allowedOrigins("http://example.com") //允许的源
.allowCredentials(true) //是否允许发送凭证
.allowedMethods("GET","POST","PUT","DELETE") //指定允许的 HTTP 方法
.maxAge(3600 * 24); //预检请求有效期
}
4. 前端Nginx解决方案
通过 Nginx 反向代理可以有效地解决跨域问题。
假设有两个域名为 example.com 和 api.example.com,我们希望在 example.com 上通过 AJAX 请求获取 api.example.com 上的数据,但由于跨域限制,请求被浏览器拦截了。我们可以通过 Nginx 配置反向代理来解决这个问题。
首先,确保 Nginx 已经安装并运行。编辑 Nginx 的配置文件,通常位于 /etc/nginx/nginx.conf 或 /etc/nginx/sites-available/default。
server {
listen 80;
server_name example.com;
location /api {
# 定义代理目标
proxy_pass http://api.example.com;
# 允许跨域请求
add_header Access-Control-Allow-Origin *;
# 允许带身份验证信息的跨域请求
add_header Access-Control-Allow-Credentials true;
# 允许的请求方法
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
# 允许的请求头
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# 预检请求的有效期
add_header Access-Control-Max-Age 3600;
# 处理 OPTIONS 请求
if ($request_method = 'OPTIONS') {
add_header Content-Type 'text/plain; charset=utf-8';
add_header Content-Length 0;
return 204;
}
}
}
5. 两种解决方案对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Nginx | 性能高、集中配置、灵活性强 | 配置麻烦、依赖Nginx | 同域访问、微服务集中代理 |
CORS | 轻量级、细粒度控制 | 预检请求多、配置分散 | 不同域访问,需精细跨域控制 |