No ‘Access-Control-Allow-Origin‘ header is present之 为什么会跨域及解决方案

本文详细介绍了浏览器跨域请求的工作原理,包括简单请求与非简单请求的区别,并提供了多种跨域解决方案,如禁用浏览器校验、使用JSONP、服务端响应头配置等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 浏览器的限制

2 跨域

3 浏览器发送的是 XHR (XMLHttpRequest)请求

当以上三个条件都满足时浏览器会抛出跨域请求异常(记住是浏览器抛出的异常,和服务端没太大关系),在讲跨域请求解决方案前先了解几个问题。

1 http请求中,哪些是常见的简单请求,哪些是非简单请求

常见的简单请求:请求方法为:GET ,HEAD,POST,请求header里面无自定义头,Content-Type为以下几种:text/plain  multipart/form-data application/x-www-form-urlencoded

常见的非简单请求 :请求方法为:put delete的ajax请求,发送json格式的ajax请求,带自定义头的ajax请求

2 浏览器在发送跨域请求时候,会有哪些过程

如果是简单请求,浏览器会先发送请求,然后判断服务器返的返回头中是否支持跨域请求,否则抛出跨域异常

如果是非简单请求,浏览器会先发出OPTIONS请求方法的检测命令,判断服务器是否支持跨域请求,如果支持则发送真正的请求,如果不支持则抛出跨域异常,因此一个非简单请求每次会发送两个请求,后面跨域解决方案会讲到缓存OPTIONS预检请求

跨域解决方案

方案1:  禁用浏览器跨域校验,即允许跨域访问,(这种方案不可取,不可能让所有的浏览器设置允许跨域访问)

谷歌浏览器禁用跨域校验: 创建一个快捷方式发送到桌面 ,快捷方式--》右键---》属性页面中的目标输入框里追加  --disable-web-security --user-data-dir=C:\Program Files (x86)\Google\Chrome\Application (注意:--user-data-dir的值就是浏览器安装目录。)不一定生效

方案2: 采用jsonp方式,需要后台和前台同时改动代码,

1 前台需要设置callback参数,如果使用的是jquery ajax 那么dateType属性设置为jsonp,jquery框架会自动设置参数名为callback的请求参数,也可以通过jsonp属性修改jsonp请求参数名,其他js框架根据具体api使用,

2 后台接收到callback参数后认为是jsonp请求,需要返回jsonp格式,普通json请求返回的content-Type是application/json,而jsonp返回的是application/javascript,同时也证明了jsonp请求服务端返回的是js脚本

3 jsonp请求参数名前后约定需要相同,例如jquery默认使用的是callback

弊端:jsonp 需要前后端都去修改代码,且jsonp是通过动态创建script脚本发送请求,仅支持 GET方法,jsonp发出的请求不是xhr请求,也是能解决跨域的原因

方案3 服务端解决跨域问题

通过编写filter在response对象中添加响应头,告诉浏览器允许跨域访问,* 号代码允许所有的请求域名,所有的请求方法跨域访问

@WebFilter("/*")
public class CORSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        // 告诉浏览器允许所有的域访问
        // 注意 * 不能满足带有cookie的访问,Origin 必须是全匹配
        // resp.addHeader("Access-Control-Allow-Origin", "*");
        // 解决办法通过获取Origin请求头来动态设置
        String origin = request.getHeader("Origin");
        if (StringUtils.hasText(origin)) {
            resp.addHeader("Access-Control-Allow-Origin", origin);
        }
        // 允许带有cookie访问
        resp.addHeader("Access-Control-Allow-Credentials", "true");

        // 告诉浏览器允许跨域访问的方法
        resp.addHeader("Access-Control-Allow-Methods", "*");

        // 告诉浏览器允许带有Content-Type,header1,header2头的请求访问
        // resp.addHeader("Access-Control-Allow-Headers", "Content-Type,header1,header2");
        // 设置支持所有的自定义请求头
        String headers = request.getHeader("Access-Control-Request-Headers");
        if (StringUtils.hasText(headers)) {
            resp.addHeader("Access-Control-Allow-Headers", headers);
        }

        // 告诉浏览器缓存OPTIONS预检请求1小时,避免非简单请求每次发送预检请求,提升性能
        resp.addHeader("Access-Control-Max-Age", "3600");

        chain.doFilter(request, resp);
    }
}

方案4 Spring框架提供了跨域解决方案

spring提供了 @CrossOrigin注解用户解决跨域问题,同时支持全局配置
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/api/**")
			.allowedOrigins("http://domain2.com")
			.allowedMethods("PUT", "DELETE")
			.allowedHeaders("header1", "header2", "header3")
			.exposedHeaders("header1", "header2")
			.allowCredentials(false).maxAge(3600);
	}
}

方案5 服务端通过ngnix解决跨域问题 

location /{
            proxy_pass http://localhost:8080/;
           
            #告诉浏览器允许跨域访问的方法
            add_header Access-Control-Allow-Methods *;
            # 告诉浏览器缓存OPTIONS预检请求1小时
            add_header Access-Control-Max-Age 3600;
            #允许带有cookie访问
            add_header Access-Control-Allow-Credentials true;
            #注意 * 不能满足带有cookie的访问,Origin 必须是全匹配,这里通过变量获取
            add_header Access-Control-Allow-Origin $http_origin;
            #设置支持所有的自定义请求头
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            #如果预检请求,则返回成功,不需要转发到后端
            if ($request_method = OPTIONS){
                return 200;
            }
        }

方案6 客户端通过nginx隐藏跨域

#转发全部以/api开头的请求到web服务器
   location  /api
   {
        proxy_pass http://127.0.0.1:8080/api;
   }

### 解决因缺少 `Access-Control-Allow-Origin` 头而导致的 CORS 错误 CORS(资源共享)是一种用于控制浏览器如何允许网页从不同的源加载资源的安全机制。当服务器响应中未包含必要的 `Access-Control-Allow-Origin` 头时,浏览器会阻止前端应用访问这些资源,并抛出 CORS 错误。 --- #### 为什么会出现此问题? 当客户端(通常是浏览器中的 JavaScript 应用程序)尝试向另一个源(即具有不同协议、名或端口号的服务器)发出 HTTP 请求时,浏览器会在后台执行一系列检查来确认目标服务器是否允许此类请求。如果没有设置适当的 CORS 响应头,例如 `Access-Control-Allow-Origin`,则请求会被拒绝[^7]。 --- #### 如何解决? ##### 方法一:在后端添加 CORS 响应头 最直接的方式是在服务器端配置相应的 HTTP 响应头以支持请求。下面是一些常见编程框架的例子: ###### 对于 Flask: 可以手动为每个路由添加所需的 CORS 响应头。 ```python from flask import Flask, make_response app = Flask(__name__) @app.after_request def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' # 允许所有来源 response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE' # 允许的方法列表 response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' # 允许的头部字段 return response @app.route('/data', methods=['GET']) def get_data(): data = {'key': 'value'} return make_response(data) ``` 此处将 `Access-Control-Allow-Origin` 设定为通配符 (`*`) 表示任何外部站点都可以访问 API 数据;但在涉及敏感信息的情况下应该指定确切的 URL 地址而不是使用星号[^8]。 ###### 对于 Node.js/Express: 同样可以通过中间件实现这一目的。 ```javascript const express = require('express'); const app = express(); // 添加 CORS 支持 app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); next(); }); app.get('/data', (req, res) => { const data = { key: 'value' }; res.json(data); }); ``` --- ##### 方法二:利用反向代理规避 CORS 限制 另一种方法是通过同一源上的反向代理转发请求到实际的服务端点。这样做的好处是可以完全绕过浏览器级别的 CORS 检查,因为所有的通信都发生在同一个主机内部。 假设我们正在运行一个 Nginx 作为我们的 Web 服务器兼反向代理角色,则可以在其配置文件中加入如下内容: ```nginx server { listen 80; server_name localhost; location /api/ { proxy_pass http://backend_server/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` 在这个例子当中,所有发往 `/api/...` 路径下的请求都会被重定向至名为 `backend_server` 的上游服务那里去处理[^9]。 --- ##### 方法三:修改客户端代码 虽然这不是最佳实践,但如果无法更改远程API的行为模式的话,也可以考虑采用JSONP技术或者iframe沙箱等方式间接获取所需的数据。不过需要注意的是这两种方式都有各自的局限性和安全隐患,所以一般只适合非常特殊的情况之下才选用它们[^10]。 --- ### 结论 为了有效应对由于缺乏必要首部而引发的读取失败状况,应当优先调整后端逻辑,在回应消息里附加恰当形式化的权限声明项——主要是指明许可范围内的原始地址以及所接纳的动作类别等内容。与此同时也要留意保护好个人隐私资料免受未经授权方窥探的风险。 ---
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值