概念介绍
跨域是由于浏览器的同源策略所导致的一种安全机制。同源策略要求网页在发送网络请求时,api的协议、域名、端口必须与网页完全相同,否则浏览器就会阻止这些请求,这就是跨域限制。
跨域是浏览器特有的一种特性,在服务端并不存在跨域的概念。服务器之间进行通信时,只要网络可达、权限允许,就可以自由地交互数据。
开发环境设置跨域
由于跨域是浏览器所特有,在服务器上不存在跨域。基于这样的原理,可以利用 Vite 内置的服务模块来设置代理,以此巧妙地绕过跨域限制。当前端需要向不同源的api发起请求时,Vite 内置的HTTP服务就开始发挥作用了。
开发环境下,axios请求会先发送到 Vite 启动的本地开发服务器( 提供localhost服务的server),而不是直接发送到真实地址。然后,开发服务器会根据我们预先配置好的代理规则,将这个请求转发到对应的后端服务器。预先配置好的代理规则可以如下。
// vite.config.ts 配置
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8083',
//转发时改变header中的Origin,使其与后端源(http://localhost:8083)相匹配
changeOrigin: true,
//用正则将请求路径开头的'/api'替换为空(修正为真实的api地址)
rewrite: (path) => path.replace(/^\/api/, "")
}
}
},
});
axios配置
import axios, { AxiosInstance } from "axios";
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 12000
});
// 开发环境:VITE_BASE_URL="/api"
// 生产环境:VITE_BASE_URL="http://domain:port"
基于以上配置,当本地服务器收到以'/api
'开头的请求时,会将该请求转发到localhost:8083
,即真实的后端地址。
比如,axios发起请求http://localhost:5173/api/users
,Vite 会将这个请求代理到 http://localhost:8083/users
,并修改Header中的 Origin
,同时对请求路径调整,确保请求能够被后端正确处理。
这也是为什么开发环境下的请求地址都是http://localhost:5173/api/xxx,而非真实后端端口。
生产环境设置跨域
一、Spring的CorsFilter
浏览器通常会因为同源策略限制这种交互,除非服务器明确允许。而服务器明确允许指的就是CORS!
跨域资源共享机制(CORS)允许服务器向浏览器声明哪些跨域请求是允许的。它通过服务器在响应头中添加特定的信息来实现,当浏览器看到这些信息,就会允许相应的跨域请求。
特定的信息包括:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Allow-Credentials
- Access-Control-Max-Age
- Access-Control-Expose-Headers
Access-Control-Allow-Origin:指定服务器允许的请求源(Origin),可以是一个或多个源( http://xxx.com
),也可以是 *
允许任何源,但是*不安全。
Access-Control-Allow-Methods:指定允许的 HTTP 请求方法,包括 GET
、POST
、PUT
、DELETE
、OPTIONS
等。
Access-Control-Allow-Headers:指定允许的请求头。可以是具体的请求头列表,也可以是 *
表示允许任何请求头(不安全)。
Access-Control-Allow-Credentials:指定是否允许请求前端携带凭证,如 cookie
、HTTP 认证信息等。只能为true或false;为true时第一个字段值不能为*。
Access-Control-Max-Age:指定 OPTIONS
请求的缓存时长。在这个时长内,浏览器会使用缓存的预检请求结果,而不是每次都发送预检请求。指定 OPTIONS
请求的缓存时长。
Access-Control-Expose-Headers:指定哪些响应头可以暴露给浏览器。
例如,当浏览器发起一个跨域的 POST
请求时,浏览器会先检查响应中的 Access-Control-Allow-Origin
是否包含请求的源,Access-Control-Allow-Methods
是否包含 POST
方法,Access-Control-Allow-Headers
是否包含请求的请求头,以及其他相关信息......如果不满足,浏览器会阻止请求,并可能抛出一个错误。
跨域问题仅靠前端无法解决(前端仔泪目😭),需要后端配置。通过以下过滤器可以在服务器端添加相应的响应头,以允许跨域请求。CORS 机制中,当网页需要发送一个跨域的请求时,浏览器会在真实发送之前,先发送一个 OPTIONS
请求询问服务器是否允许真实请求
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CorsFilter implements Filter {
// 先执行过滤器,后执行拦截器
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, Origin, X-Requested-With, cache-control, pragma, proxy-connection, User-Agent");
response.setHeader("Access-Control-Allow-Credentials", "true");
//if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
// response.setStatus(HttpServletResponse.SC_OK);
// return;
//}
chain.doFilter(req, res);
}
}
前端只要满足上述配置,请求方法只有GET、POST、OPTIONS,不包括PUT啦,DELETE啦,以及不添加一些奇奇怪怪的请求头啦,是可以进行跨域的~~
(注释的方法很作弊,直接对所有的OPTIONS返回ok,我就是这么干的
浏览器会检查预检响应头是否包含特定字段,以决定继续请求或是丢弃响应结果。
二、Nginx 反向代理
生产环境无法设置开发代理,但是可以设置Nginx 反向代理。Nginx 可以作为中间层将客户端请求转发到后端,并添加跨域头。
server {
listen 80;
server_name xxx.com;
location / {
if ($request_method = 'OPTIONS') {
# 设置允许的源仅允许 https://xxx.com
add_header 'Access-Control-Allow-Origin' 'https://xxx.com';
# 允许的方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# 允许的请求头
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
# 是否允许携带凭证
add_header 'Access-Control-Allow-Credentials' 'true';
# 对于OPTIONS,返回 204 No Content
return 204;
}
# 将请求转发到后端服务
proxy_pass http://xxx:yyy;
}
}
以上配置Nginx 会对 OPTIONS
做特殊处理,添加跨域头,然后将请求转发到后端(http://xxx)
。这样可以将跨域问题从后端分离出来,由 Nginx 进行统一处理。