问题描述
当前端发起一个请求,这个请求的接口满足跨协议、跨域名、跨端口任意一个时候都会触发浏览器的同源策略(Same-Origin Policy),其核心目的是防止恶意网站通过脚本(如 JavaScript)非法访问其他域的资源(如数据、Cookie 等),从而保护用户隐私和数据安全。
同源
两个 URL 的 协议(Protocol)、域名(Domain)、端口(Port) 必须完全一致,才属于同源。
https://example.com/page1 和 https://example.com/page2 ✅ 同源(协议、域名、端口一致)
http://example.com 和 https://example.com ❌ 不同源(协议不同)
example.com 和 sub.example.com ❌ 不同源(域名不同)
example.com:80 和 example.com:8080 ❌ 不同源(端口不同)
触发场景
当浏览器通过 JavaScript 发起以下类型的请求时,若目标 URL 与当前页面不同源,就会触发跨域限制:
- AJAX/Fetch 请求(如
XMLHttpRequest
或fetch()
) - WebSocket 请求
- 跨域资源嵌入(如
<script>
、<img>
、<link>
标签加载外部资源时,浏览器允许加载但会限制 JavaScript 读取内容) - 跨域 DOM 操作(如
iframe
嵌套不同源页面时的通信)
跨域问题原理
浏览器的同源策略通过以下机制实现:
请求可以发送,但响应被拦截 浏览器允许发送跨域请求,但会检查服务器的响应头中是否包含允许跨域的标记(如
Access-Control-Allow-Origin
)。如果未通过检查,浏览器会阻止 JavaScript 读取响应内容。预检请求(Preflight Request) 对于可能对服务器数据产生副作用的复杂请求(如
PUT
、DELETE
或自定义请求头),浏览器会先发送一个 OPTIONS 请求(预检请求),询问服务器是否允许实际请求。只有服务器明确授权后,浏览器才会发送真正的请求。
示例流程:
浏览器 → 发送 OPTIONS 预检请求 → 服务器
浏览器 ← 检查响应头(如 Access-Control-Allow-Methods) ← 服务器
(如果通过)浏览器 → 发送真实请求(如 POST) → 服务器
Cookie、LocalStorage 等存储数据默认不可跨域访问。
iframe
中的跨域页面无法通过 JavaScript 直接操作父页面或子页面的 DOM。
解决方案
JSONP (仅限 GET 请求)
前端:
$.ajax({
url: 'http://localhost:8080/info?id=666',
type: 'GET',
dataType: 'jsonp', // 期望从服务器返回的数据类型
jsonp: 'lettle', // 指定参数名
success: function(resoponse) {
alert(response.data);
}
})
后端:
@RequestMapping(value="/info", method = RequestMethod)
@ResponseBody
public JSONPObject Info(@RequestParam("id") String id, String lettle) {
Info info = infoService.selectInfoById(id);
return new JSONPObject(lettle, info);
}
服务端改动
前端:
$.ajax({
url: 'http://localhost:8080/info?id=666',
type: 'GET',
dataType: 'json', // 期望从服务器返回的数据类型
success: function(resoponse) {
alert(response.data);
}
})
后端:
@RequestMapping(value="/info", method = RequestMethod)
@ResponseBody
@CrossOrigin("http://localhost:8090")
public Info Info(@RequestParam("id") String id, String lettle) {
Info info = infoService.selectInfoById(id);
return info;
}
在方法或者控制类上面使用
@CrossOrigin
注解,还能指定哪个端口
Config
@Configuration
public class MyConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/info") // 哪些接口
.allowedOrigins("http://localhost:8090/") // 允许哪些域名
.allowedMethods("GET"); // 访问类型
}
}
或者自定义 Filter 手动设置响应头:
@Configuration
public class MyConfig2 {
@Bean
public Filter corsFilter() {
return new Filter() {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, res);
}
// 其他方法空实现即可
@Override public void init(FilterConfig filterConfig) {}
@Override public void destroy() {}
};
}
}
Nginx
在Nginx里设置一些跨域响应头
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Origin' 1728000;