Ajax请求中的跨域问题及其解决方案

news2024/11/27 13:10:57

跨域问题

跨域是指从一个域名的网页去请求另一个域名的资源, 比如当前在百度页面(https://baidu.com)去请求京东服务器(https://www.jd.com)的资源

在这里插入图片描述

传统请求不会跨域

a站点可以通过超链接或者form表单提交或者window.location.href的方式跨域访问b站点的资源(静态或者动态)

  • 传统方式本质都是在地址栏上发了一次请求直接进行页面跳转,并不是从当前页面获取另一个页面的资源
<!--通过超链接的方式发送请求可以跨域-->
<a href="http://localhost:8081/b/index.html">跨域访问b站点的index页面</a>
<br>
<!--通过form表单的方式发送请求可以跨域-->
<form action="http://localhost:8081/b/user/reg" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="注册">
</form>
<br>
<!--通过js代码中的window.location.href/document.location.href的方式发送请求可以跨域-->
<button onclick="window.location.href='http://localhost:8081/b/index.html'">跨域访问b站点的index页面</button>
<button onclick="document.location.href='http://localhost:8081/b/index.html'">跨域访问b站点的index页面</button>
<!--使用script标签的src属性可以跨域加载js文件-->
<script type="text/javascript" src="http://localhost:8081/b/my.js"></script>
<br>
<!--img的src属性也可以跨域加载其他站点的图片-->
<img src="http://localhost:8081/b/bd_logo.png" />

b站点的静态资源index.html页面

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>b应用的index页面</title>
    </head>
    <body>
        <h1>b应用的index页面</h1>
    </body>
</html>

b站点的动态资源UserRegServlet

@WebServlet("/user/reg")
public class UserRegServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 响应到前端
        response.getWriter().print(username + ":" + password);
    }
}

Ajax请求跨域问题

a站点的页面中发送Ajax请求访问b站点的资源,此时发起的请求就是跨域请求

<script type="text/javascript">
    // 使用ES6新特性:箭头函数
    window.onload = () => {
        document.getElementById("btn").onclick = () => {
            // 1. 创建核心对象(ES6的新特性:var let const关键字都可以定义变量)
            let xmlHttpRequest = new XMLHttpRequest();
            // 2. 注册回调函数
            xmlHttpRequest.onreadystatechange = () => {
                if (xmlHttpRequest.readyState == 4) {
                    // 状态码在这个范围内都可以
                    if (xmlHttpRequest.status >= 200 && xmlHttpRequest.status < 300) {
                        document.getElementById("mydiv").innerHTML = xmlHttpRequest.responseText
                    }
                }
            }
            // 3. 开启通道
            xmlHttpRequest.open("GET", "http://localhost:8081/b/hello", true)
            // 4. 发送请求
            xmlHttpRequest.send()
        }
    }
</script>
<button id="btn">发送ajax跨域请求</button>
<div id="mydiv"></div>
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 响应
        response.getWriter().print("hello ajax!!!");
    }
}

由于浏览器的同源策略,浏览器会把服务器响应的数据拦截导致页面无法渲染服务器响应的数据

# chrome浏览器报错
Access to XMLHttpRequest at 'http://localhost:63110/system/dictionary/all' from origin 'http://localhost:8601' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

# firefox浏览器报错
已拦截跨源请求:同源策略禁止读取位于 http://localhost:63110/system/dictionary/all 的远程资源(原因:CORS 头缺少 'Access-Control-Allow-Origin')

AJAX跨域解决方案

有时候我们是需要使用ajax进行跨域访问的,比如某公司的A页面(a.bjpowernode.com)需要获取B页面(b.bjpowernode.com)的资源

同源策略(CORS policy)

同源策略是指一段脚本只能读取来自同一来源的窗口和文档的属性有利于保护网站信息)

  • 同源就是协议、域名、端口都相同,即便两个不同的域名指向同一个ip地址也非同源
  • 如果没有同源策略,当你在网银站点输入了账号密码,然后访问了一些不规矩的网站,那么这些网站就可以访问刚刚的网银站点获取你的账号密码

当从一个地址请求另一个地址时,如果协议,域名,端口号都一致时表示同源,有一个不一致就是跨域请求,此时浏览器会在请求头上添加origin

  • 同源策略是浏览器的一种安全机制,浏览器基于同源策略去判断用户发起的是否为跨域请求,避免浏览器受到 XSS、CSRF 等攻击
  • 跨域请求只发生在浏览器和服务器之间,服务器之间不存在跨域请求
  • 跨域并不是请求发不出去,请求能发出去并且服务端也能收到请求并正常返回结果,只是响应的数据被浏览器拦截了,因为浏览器认为你发起的是跨域请求
  • 跨域的时候浏览器不允许共享同一个XMLHttpRequest对象, 因为共享同一个XMLHttpRequest对象是不安全的
URL1URL2是否同源描述
http://localhost:8080/a/index.htmlhttp://localhost:8080/a/first同源协议 域名 端口一致
http://localhost:8080/a/index.htmlhttp://localhost:8080/b/first同源协议 域名 端口一致
http://www.myweb.com:8080/a.jshttps://www.myweb.com:8080/b.js不同源协议不同
http://www.myweb.com:8080/a.jshttp://www.myweb.com:8081/b.js不同源端口不同
http://www.myweb.com/a.jshttp://www.myweb2.com/b.js不同源域名不同
http://www.myweb.com/a.jshttp://crm.myweb.com/b.js不同源子域名不同

CORS(响应头)方案

CORS(Cross-origin resource sharing)即跨域资源共享,它允许浏览器向跨源服务器发出XMLHttpRequest请求从而克服了AJAX只能同源使用的限制

服务端向浏览器响应数据时添加响应头Access-Control-Allow-Origin,即告诉浏览器允许哪些站点跨域访问资源,这样浏览器就会放行对应的站点

response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080"); // 允许某个站点
response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有站点

jsonp方案

jsonp(json with padding)表示其是带填充的json,类似ajax请求可以以完成局部刷新效果并且可以解决跨域问题,但是只支持GET请求,不支持POST请求
在这里插入图片描述

通过script标签的src属性进行跨域请求,服务器响应的JS代码会在script标签内解析

  • script标签的src属性可以跨域访问静态的xxx.js资源文件,也可以跨域请求一个动态的Java程序,请求响应的结果都会在script标签内解析
<script type="text/javascript">
    // 自定义函数
    function sayHello(data){
        alert("hello," + data.name)
    }
</script>
<!---->
<script type="text/javascript" src="http://localhost:8081/b/jsonp?fun=sayHello">
	// 服务器响应的JS代码会在script标签内解析
    sayHello({"name" : "jackson"})
</script>

第一步: 页面打开的时候先不创建script标签,当我们点击按钮的时候再创建script元素并设置src属性发送jsonp请求实现页面局部刷新效果

<!--<script type="text/javascript" src="http://localhost:8081/b/jsonp?fun=sayHello"></script>-->
<script type="text/javascript">
  // 自定义的函数, 参数data是一个json对象
  function sayHello(data){ 
    document.getElementById("mydiv").innerHTML = data.username
  }
  window.onload = () => {
    document.getElementById("btn").onclick = () => {
      // 创建script元素对象
      const htmlScriptElement = document.createElement("script");
      // 设置script的type属性
      htmlScriptElement.type = "text/javascript"
      // 设置script的src属性
      htmlScriptElement.src = "http://localhost:8081/b/jsonp?fun=sayHello"
      // 将script对象添加到body标签中(这一步就是加载script)
      document.getElementsByTagName("body")[0].appendChild(htmlScriptElement)
    }
  }
</script>
<button id="btn">jsonp解决跨域问题,达到ajax局部刷新的效果</button>
<div id="mydiv"></div>

第二步: 后端处理前端发起的jsonp请求并响应一段JS代码,后端响应的永远都是字符串,但是浏览器接会自动将这个字符串当做一段JS代码在script标签内解析并执行

@WebServlet("/jsonp")
public class JSONPServlet1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 向前端响应一段js代码
        PrintWriter out = response.getWriter();
        //响应一个JS内置的alert函数,浏览器解析后可以直接调用用
        //out.print("alert(123)"); 
        //直接响应一个程序员自定义的sayHello函数,参数是一个json对象
        //out.print("sayHello({\"name\" : \"jackson\"})");
        //动态获取URL中请求参数对应的函数名,方法参数是一个json对象
        String fun = request.getParameter("fun");
        out.print(fun + "({\"name\" : \"jackson\"})");
    }
}

jQuery封装的jsonp

直接使用jQuery封装的jsonp , 底层原理不变

<!--引入官方的jQuery库是官网的-->
<script type="text/javascript" src="/ajax/js/jquery-3.6.0.min.js"></script>

<script type="text/javascript">
    // 这个函数不用你写,jQuery可以帮助你自动生成回调
    function jQuery3600508253314856699_1655528968612(json){ 
   		// 会继续自动调用success的回调函数
    }

    // 自定义的函数
    function sayHello(data){
        $("#mydiv").html("欢迎你:" + data.username)
    }

    $(function(){
        $("#btn").click(function(){
            // 发送类似ajax的请求,本质上并不是一个ajax请求
            $.ajax({
                type : "GET", // jsonp请求只支持get请求
        		// 实际上发送的请求是/b/jsonp?callback=jQuery3600508253314856699_1655528968612&_=1655528968613(时间戳)
                // callback是请求参数名,jQuery3600508253314856699_1655528968612是随机函数名
                url : "http://localhost:8081/b/jsonp",//指定跨域的url
                dataType : "jsonp", // 指定数据类型是jsonp形式
                jsonp : "fun", //  指定具体的请求参数名(之前的fun), 默认的请求参数名是callback
                jsonpCallback : "sayHello"  // 指定自定义的回调函数名(之前的sayHello),默认jQuery会自动生成一个随机的回调函数
                
                // jQuery自动生成的回调函数会自动调用success的回调函数
                /*success : function(data){ // data是一个json对象用来接收服务器端响应的数据
                    $("#mydiv").html("欢迎你:" + data.username)
                }*/
            })
        })
    })
</script>
<button id="btn">jQuery库封装的jsonp</button>
<div id="mydiv"></div>

代理机制原理

本站点的页面访问当前站点的ProxyServlet不会有跨域问题,然后通过ProxyServlet发送GET/POST请求访问跨域的资源

  • 第一种方案(代码繁琐): 使用JDK内置的API(java.net.URL)发送HTTP请求
  • 第二种方案(推荐): 使用第三方的开源组件发送http请求,比如apache开源免费的httpclient组件

在这里插入图片描述

第一步: 在a站点的页面中访问当前站点中的ProxyServlet

<script type="text/javascript">
    // ES6当中的有一个新语法箭头函数
    window.onload = () => {
        document.getElementById("btn").onclick = () => {
            // 1.创建核心对象
            const xmlHttpRequest = new XMLHttpRequest(); 
            // 2.注册回调函数
            xmlHttpRequest.onreadystatechange = () => {
                if (xmlHttpRequest.readyState == 4) {
                    // 这里也可以使用区间的方式,因为状态码是200~299都是正常响应结束
                    if (xmlHttpRequest.status >= 200 && xmlHttpRequest.status < 300) {
                        document.getElementById("mydiv").innerHTML = xmlHttpRequest.responseText
                    }
                }
            }
            // 3.开启通道
            xmlHttpRequest.open("GET", "/a/proxy", true)
            // 4.发送请求
            xmlHttpRequest.send()
        }
    }
</script>
<button id="btn">使用代理机制解决ajax跨域访问</button>
<div id="mydiv"></div>

第二步: 在a站点的ProxyServlet中通过httpclient组件发送GET请求去访问b站点的TargetServlet

  • b站点的TargetServlet会将数据响应给a站点的ProxyServlet,然后ProxyServlet再把数据响应给前端
@WebServlet("/proxy")
public class ProxyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 目标地址
        HttpGet httpGet = new HttpGet("http://localhost:8081/b/target");
        // 设置类型 "application/x-www-form-urlencoded" "application/json"
        httpGet.setHeader("Content-Type", "application/x-www-form-urlencoded");
        //System.out.println("调用URL: " + httpGet.getURI());
        
        // httpClient实例化
        CloseableHttpClient httpClient = HttpClients.createDefault();
		// 发起请求访问b站点的TargetServlet
        HttpResponse resp = httpClient.execute(httpGet);
        // 获取TargetServlet响应给ProxyServlet的数据
        HttpEntity entity = resp.getEntity();
        System.out.println("返回状态码:" + response.getStatusLine());
        // 显示TargetServlet响应给ProxyServlet的数据
        BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
        String line = null;
        StringBuffer responseSB = new StringBuffer();
        while ((line = reader.readLine()) != null) {
            responseSB.append(line);
        }
        //System.out.println("服务器响应的数据:" + responseSB);
        reader.close();
        httpClient.close();
        // 将b站点的TargetServlet响应回来的数据继续响应给前端
        response.getWriter().print(responseSB);
    }
}

第三步: b站点的TargetServlet,处理a站点ProxyServlet发起的ajax请求

@WebServlet("/target")
public class TargetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 响应一个json字符串
        response.getWriter().print("{\"username\":\"jackson\"}");
    }
}

Nginx反向代理

代理服务器方案的实现原理: 同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略的

在这里插入图片描述

Nginx的反向代理也是使用了类似ProxyServlet的代理机制来完成Ajax的跨域

  • 第一步: 浏览器先访问Nginx提供的前端地址页面http://192.168.101.10:8601
  • 第二步: 前端页面发起的请求会先访问Nginx提供的一个同源地址(类似ProxyServlet)http://192.168.101.11:8601/api/前端发起的具体请求
  • 第三步: 由于服务端之间没有跨域,所以可以通过ProxyServlet实现跨域访问目标地址http://www.baidu.com:8601

在这里插入图片描述

其他常见方案

常见代理方案

  • Node 中间件代理, postMesssage, websocket, window.name + iframe , location.hash + iframe, document.domain + iframe等
  • vue-cli(Vue 脚手架自带的 8080 服务器也可以作为代理服务器,但是需要通过配置vue.config.js来启用这个代理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1190260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

35岁危机来临前,程序员如何未雨绸缪?

程序员逼近35岁”高龄“&#xff0c;救命。。。 &#xff08;目瞪口呆)什么&#xff1f; 程序员而立之年&#xff0c;为未来担忧&#xff1f;&#xff08;双手抱头不敢置信&#xff09; 不可能&#xff01;他们明明那么努力、那么辛苦了&#xff01;&#xff01;&#xff01;&a…

用趋动云GPU部署自己的Stable Diffusion

注&#xff1a;本文内容来自于对DataWhale的开源学习项目——免费GPU线上跑AI项目实践的学习&#xff0c;参见&#xff1a;Docs&#xff0c;引用了多处DataWhale给出的教程。 1.创建项目 1&#xff09;进入趋动云用户工作台&#xff0c;在当前空间处选择注册时系统自动生成的…

debian/ubuntu/windows配置wiregurad内网服务器(包含掉线自启动)

文章目录 前言一、服务器配置安装wireguard软件生成私钥公钥配置服务器参数配置服务器sysctl参数启动、停止服务端 二、用户端配置安装wireguard软件生成私钥公钥配置客户端参数启动、停止客户端配置服务开机启动 三、服务器添加、删除客户四、配置掉线自启动配置掉线自启动脚本…

基于轻量级卷积神经网络CNN开发构建打架斗殴识别分析系统

在很多公共场合中&#xff0c;因为一些不可控因素导致最终爆发打架斗殴或者大规则冲突事件的案例层出不穷&#xff0c;基于视频监控等技术手段智能自动化地识别出已有或者潜在的危险行为对于维护公共场合的安全稳定有着重要的意义。本文的核心目的就是想要基于CNN模型来尝试开发…

MySQL中的多列子查询

-- 多列子查询 -- 如何查询与WOARD 的部门和岗位完全相同的所有雇员(并且不含smith本人) -- (字段1&#xff0c;字段2...) (select 字段1&#xff0c;字段2 from ...) -- 分析&#xff1a; 1. 得到smith的部门和岗位 SELECT deptno,job FROM empWHERE ename WARD; -- 2.使…

字符编码转换时发生内存越界引发的摄像头切换失败问题的排查

目录 1、问题说明 2、初步分析 3、字符串字符编码说明 4、进一步分析 5、为啥在日常测试时没有遇到切换摄像头失败的问题呢&#xff1f; 6、华为MateBook笔记本使用高通的CPU 7、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更…

电脑msvcp110.dll丢失怎么办,msvcp110.dll缺失的详细修复步骤

在现代科技发展的时代&#xff0c;电脑已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;由于各种原因&#xff0c;电脑可能会出现一些问题&#xff0c;其中之一就是msvcp110.dll文件丢失。这个问题可能会导致一些应用程序无法正常运行&#xff0c;给我们的生活和工作…

[直播自学]-[汇川easy320]搞起来(3)看文档安装软件 查找设备

2023.11.09 20&#xff1a;04 按照文档 解压压缩包得到&#xff1a; 打开 里面有一条值得注意&#xff1a; 想把软件安装到C盘&#xff0c;但是C盘没什么空间了&#xff0c;把C盘清理清理。 20&#xff1a;35 安装很快完成&#xff0c;然后阅读 由于PLC是新的&#xff0c…

【MATLAB源码-第70期】基于matlab的萤火虫算法(FA)的栅格路径规划,输出最短路径和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 萤火虫算法&#xff08;Firefly Algorithm&#xff0c;FA&#xff09;是由剑桥大学的Xin-She Yang在2008年提出的一种元启发式优化算法。该算法的灵感来源于萤火虫闪烁的行为特征&#xff0c;主要用于解决连续的优化问题。萤…

【123. 买卖股票的最佳时机 III】

目录 一、题目描述二、算法原理三、代码实现 一、题目描述 二、算法原理 三、代码实现 class Solution { public:const int Init-0x3f3f3f3f;int maxProfit(vector<int>& prices) {int nprices.size();vector<vector<int>> f(n,vector<int>(3,Ini…

使用TS进行Vue-Router的Meta类型扩展

文章目录 1、前言2、解决 1、前言 使用Vue-Router时&#xff0c;会将一些字段信息附加到路由的Meta对象里面&#xff0c;比如图标icon&#xff0c;标题&#xff0c;权限等&#xff0c;如下&#xff1a; {path: /billboard/board/:boardId,name: billboardBoard,props: true,c…

超全总结!大模型算法面试指南(含答案)

大家好&#xff0c;从 2019 年的谷歌 T5 到 OpenAI GPT 系列&#xff0c;参数量爆炸的大模型不断涌现。可以说&#xff0c;LLMs 的研究在学界和业界都得到了很大的推进&#xff0c;尤其去年 11 月底对话大模型 ChatGPT 的出现更是引起了社会各界的广泛关注。 近些年&#xff0…

java项目之服装定制系统(ssm框架)

项目简介 服装定制系统实现了以下功能&#xff1a; 管理员&#xff1a;管理员使用本系统涉到的功能主要有首页、个人中心、用户管理、服装类型管理、服装信息管理、服装定制管理、留言反馈、系统管理等功能。用户&#xff1a;用户进入系统可以对首页、个人中心、服装定制管理…

Java类和对象(1)

&#x1f435;本篇文章将会开始对类和对象的第一部分讲解 一、简单描述类和对象 对象可以理解为一个实体&#xff0c;在现实生活中&#xff0c;比如在创建一个建筑之前&#xff0c;要先有一个蓝图&#xff0c;这个蓝图用来描述这个建筑的各种属性&#xff1b;此时蓝图就是类&a…

C++day6作业

1.思维导图 2.编程题&#xff1a; 以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在&am…

后台管理系统解决方案-中大型-Vben Admin

后台管理系统解决方案-中大型-Vben Admin 官网 Vben Admin 在线演示 Vben Admin 为什么选择它 github现有20K星&#xff0c;并且它有个可视化生成表单&#xff0c;我很喜欢 快速开始 # 拉取代码 git clone https://github.com/vbenjs/vue-vben-admin-doc# 安装依赖 yarn#…

天津WEB前端培训哪家好?Web机构推荐!

05年以后&#xff0c;互联网已经进入了web2.0时代&#xff0c;同时也标志着网站的前端由此发生了翻天覆地的变化&#xff0c;现在市场上对WEB前端开发工程师岗位有着很大的需求&#xff0c;学习web前端开发的方式有很多种&#xff0c;对于初学者来说&#xff0c;选择自学还是培…

使用篇(一):Ai绘图-Stable Diffusion WebUI

1.介绍 1.1 概述 Stable Diffusion Web UI是一个基于Stable diffusion AI模型的AI绘画软件。它是一个多功能的AI绘画软件&#xff0c;支持以下几个功能&#xff1a; 用户可以输入一堆关键词或一句话来生成图片。 它使用了图像加噪去噪过程中的生成模型—— Duffusion&#xff…

与set和map相关的OJ题练习

一、两个数组的交集 题目链接&#xff1a; 349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给两个数组&#xff0c;求在数组里面共同出现的部分&#xff0c;就是求两个数组的交集&#xff0c;返回顺序不做要求 解题思路&#xff1a; …