您的位置 首页 java

Ajax跨域问题详解

1.什么是 Ajax 跨域问题

客户端Client通过Ajax方式向服务器Server发送Ajax请求,想要得到响应数据,但是由于客户端和服务器不在同一个域(协议,域名或端口不一致),浏览器出于安全方面的考虑,会在Ajax请求的时候作校验,校验不通过时浏览器会在控制台会抛出一个类似于SEC7120: [CORS] 原点“”未在“”的 cross-origin资源的 Access-Control-Allow-Origin response header 中找到“”的跨域安全问题

2.为什么会产生Ajax跨域问题

*浏览器限制:通俗一点讲就是浏览器多管闲事,当发现客户端和服务器不在同一个域中时会对Ajax请求做校验,校验不通过就会产生跨域安全问题,并非服务器不允许客户端访问(以下示例可以验证)。

*跨域:当客户端和服务器的协议,域名,端口有一样不一致时,浏览器就会认为是跨域。

*XMLHttpRequest请求(Ajax请求):如果发出的不是XMLHttpRequest请求,浏览器也不会报跨域问题(以下示例可以验证)。

3.Ajax跨域问题示例(基于SpringBoot)

创建服务器端:

[ java ] view plain copy

  1. import org. spring framework.web.bind.annotation.RequestMapping;
  2. import org.springframework.web.bind.annotation.RestController;
  3. @RestController
  4. @RequestMapping(“/ajaxserver”)
  5. public class AjaxServer Controller {
  6. @RequestMapping(“hello”)
  7. public String getString(){
  8. System.out.println(“************”);
  9. return “Hello world!”;
  10. }
  11. }

添加配置:

[html] view plain copy

  1. #配置端口号
  2. server.port=8081
  3. #热部署生效
  4. spring.devtools.restart.enabled=true

验证服务器端(浏览器访问):

创建客户端:

[java] view plain copy

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. @Controller
  4. @RequestMapping(“/ajaxclient”)
  5. public class AjaxClientController {
  6. @RequestMapping(“/index”)
  7. public String getIndex(){
  8. return “index”;
  9. }
  10. }

创建静态页面index.html

[html] view plain copy

  1. <!DOCTYPE html >
  2. <html xmlns:th=”” >
  3. <head>
  4. <meta charset=”UTF-8″ >
  5. <title> Ajax跨域请求 </title>
  6. <!– <script type=”text/javascript” src=”js/jquery-3.2.1.min.js”></script> –>
  7. <!– 引入静态资源文件–>
  8. <script th:src=”@{/static/js/jquery-3.2.1.min.js}” type=”text/javascript” ></script>
  9. </head>
  10. <body>
  11. <a href=”#” onclick=”getRequest();” > Ajax跨域请求 </a>
  12. <script type=”text/javascript” >
  13. function getRequest(){
  14. console.log(“getRequest”);
  15. $.ajax({
  16. url:””,
  17. dataType:”JSON”,
  18. type:”POST”,
  19. async:true,
  20. success:function(data){
  21. console.log(data);
  22. }
  23. });
  24. }
  25. </script>
  26. </body>
  27. </html>

添加配置:

[html] view plain copy

  1. ############################################################
  2. #
  3. # thymeleaf相关配置
  4. #
  5. ############################################################
  6. spring.thymeleaf.prefix=classpath:/templates/
  7. spring.thymeleaf.suffix=.html
  8. spring.thymeleaf.mode=HTML5
  9. spring.thymeleaf.encoding=UTF-8
  10. spring.thymeleaf.content-type=text/html
  11. #加载静态资源文件
  12. spring.mvc.static-path-pattern=/static/**

引入依赖:

[html] view plain copy

  1. <!– 引入thymeleaf依赖 –>
  2. <dependency>
  3. <groupId> org.springframework.boot </groupId>
  4. <artifactId> spring-boot-starter-thymeleaf </artifactId>
  5. </dependency>

启动客户端,访问

点击Ajax跨域问题链接,可以看到如下信息:

浏览器控制台抛出如下错误信息:SEC7120: [CORS] 原点“”未在“”的 cross-origin 资源的 Access-Control-Allow-Origin response header 中找到“”。

此时,清空编辑器控制台信息,来验证跨域问题并非服务器不允许客户端访问。再一次请求服务器,可以看到,编辑器控制台打印信息如下:

此时,F12进入浏览器调试模式查看网络,服务器没有响应数据

在index.html中添加如下链接来验证如果发出的不是XMLHttpRequest请求,浏览器也不会报跨域问题

[html] view plain copy

  1. <a href=”” > 非Ajax请求 </a>

访问可以看到如下信息:

点击非Ajax请求链接,可以看到如下信息:

控制台没有抛出Ajax跨域安全问题,并且服务器返回了响应数据

4.Ajax跨域问题解决思路

Ajax跨域问题产生的原因是浏览器限制,Ajax请求以及跨域,当三者同时满足时才会产生Ajax跨域问题。基于这种情况,解决思路如下:

1>不让浏览器做跨域校验:可以通过一些参数设置禁止浏览器做限制,但是这需要客户端都要做改动,因此不推荐。

2>发出不是XMLHttpRequest请求:JSONP可以动态创建一个script来发出跨域请求,但是浏览器不认为这是一个XMLHttpRequest请求。

3>支持跨域/隐藏跨域:当被调用方可以做一些修改时,被调用方可以设置参数来支持跨域(例如A域名调用B域名时,在返回的数据里面加入一些字段允许A域名调用);当被调用方不可以做一些修改时(例如需要请求www.baidu.com响应数据,而百度并非你的合作公司),此时需要调用方来做修改,通过一个代理,从浏览器发出的都是A域名的请求,在代理里面把指定的URL转到B域名里,此时浏览器认为就是同一个域名,就不会产生跨域问题。

5.Ajax跨域问题解决方法

基于Ajax跨域问题解决思路,整理了如下解决办法:

1>不让浏览器做跨域校验

可以通过命令行的方式进行设置,具体操作参考:

2>JSONP的方式

JSONP(JSON with Padding)是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。通俗地说,JSONP是非官方协议,是一种约定,它约定了如果请求的参数里包含了指定的参数(默认是callback)就是一个JSONP请求,服务器发现该请求是JSONP请求时就会把响应数据由原来的JSON对象改为JS代码(JS代码是函数调用的形式,函数名是callback参数的值,函数参数是原来要返回的JSON对象)。

将index.html中的Ajax请求的dataType改为JSONP,并修改服务器代码,添加如下类:

[java] view plain copy

  1. import org.springframework.web.bind.annotation.ControllerAdvice;
  2. import org.springframework.web. servlet .mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
  3. @SuppressWarnings(“deprecation”)
  4. @ControllerAdvice
  5. public class JSONPAdvice extends AbstractJsonpResponseBodyAdvice{
  6. public JSONPAdvice(){
  7. super (“callback”);
  8. }
  9. }

然后重新访问客户端,可以看到:

错误信息变为SCRIPT1004: SCRIPT1004: Expected ‘;’查看服务器响应数据如下:

服务器已经正常响应,但是浏览器控制台依然报错,这是为什么呢?于是百度了这个错误,得到如下结果:

由此得知,接口不支持JSONP。JSONP弊端:(1)服务器需要改动代码支持(2)SJSONP只支持GET请求(可以验证)将Ajax请求添加参数type:”POST”,然后重新访问客户端可以看到如下信息(请求的方法依然是GET):

(3)发送的不是XMLHttpRequest请求:这既是JSONP能解决跨域问题的原因,也是其弊端。因为XMLHttpRequest请求有很多新特性(例如异步,各种事件),而在JSONP中无法使用。

3>支持跨域/隐藏跨域

(1)常见的J2EE架构图

客户端Client向服务器发送请求,先经过Apache/Nginx(http服务器),当Apache/Nginx发现请求是静态请求(js文件,css文件,图片等)时,会直接将资源文件返回给客户端Client而不会再转发到Tomcat;当

[java] view plain copy

  1. import java.io.IOException;
  2. import javax.servlet.Filter;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.FilterConfig;
  5. import javax.servlet.ServletException;
  6. import javax.servlet.ServletRequest;
  7. import javax.servlet.ServletResponse;
  8. import javax.servlet.annotation.WebFilter;
  9. import javax.servlet.http.HttpServletResponse;
  10. @WebFilter(urlPatterns=”/*”)
  11. public class CrossFilter implements Filter {
  12. @Override
  13. public void init(FilterConfig filterConfig) throws ServletException {
  14. // TODO Auto-generated method stub
  15. }
  16. @Override
  17. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  18. throws IOException, ServletException {
  19. System.out.println(“CrossFilter”);
  20. HttpServletResponse res=(HttpServletResponse) response;
  21. //res.addHeader(“Access-Control-Allow-Origin”, “*”);
  22. res.addHeader(“Access-Control-Allow-Origin”, “”);
  23. //res.addHeader(“Access-Control-Allow-Methods”, “*”);
  24. res.addHeader(“Access-Control-Allow-Methods”, “POST”);
  25. chain.doFilter(request, response);
  26. }
  27. @Override
  28. public void destroy() {
  29. // TODO Auto-generated method stub
  30. }
  31. }

此过滤器的作用是将响应头添加两个字段Access-Control-Allow-Origin和Access-Control-Allow-Methods,前者表示支持跨域的域,后者表示支持跨域的方法。

修改控制层代码(注意:控制层要返回JSON格式的数据,这是因为在Ajax请求时要求响应数据必须时JSON格式, 所以在返回数据时都需要转为JSON格式)

JSONResult为LZ自己封装的工具类,源码地址

[java] view plain copy

  1. import org.springframework.web.bind.annotation.RequestMapping;
  2. import org.springframework.web.bind.annotation.RestController;
  3. import com.ajaxserver.utils.JSONResult;
  4. @RestController
  5. @RequestMapping(“/ajaxserver”)
  6. public class AjaxServerController {
  7. /**
  8. * 注意:由于在Ajax请求时要求响应数据必须时JSON格式
  9. * 所以在返回数据时都需要转为JSON格式
  10. * @return
  11. */
  12. @RequestMapping(“hello”)
  13. public JSONResult getString(){
  14. System.out.println(“AjaxServerController–>getString”);
  15. return JSONResult.ok(“Hello world!”);
  16. }
  17. }<span style=”background-color:transparent;color:rgb(79,79,79);float:none;font-size:16px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;line-height:26px;text-align:justify;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;”>
  18. </span>

然后,在项目的启动程序AjaxServcerApplication上要添加@ServletComponentScan来扫描组件(否则,过滤器不会起作用)

最后,进入浏览器访问,查看控制台,可以看到:

控制台的响应表头多了两个字段Access-Control-Allow-Origin和Access-Control-Allow-Methods,此时我们查看数据是否打印出来了:

由此说明,此次Ajax跨域访问成功响应,并将数据成功返回给客户端!

b.Apache配置

可参考

c.Nginx配置

可参考

d.Spring框架解决方案

可参考

(3)调用方解决跨域:Apache/Nginx(http服务器)将客户端Client所有的请求转发到服务器,此时浏览器发现所有的请求都是同一个域就不会产生跨域问题。a.Nginx配置可参考配置可参考

6.请求分类

(1)简单请求:浏览器对于简单请求往往先执行后判断,例如:浏览器控制台报跨域安全问题时,请求依然有响应数据,这说明浏览器将该请求视为简单请求,先执行然后再判断是否存在跨域安全问题。

(2)非简单请求:浏览器对于非简单请求往往先判断后执行,举例说明:

发送JSON格式Ajax请求示例如下:

修改index.html(添加如下代码)

[html] view plain copy

  1. <a href=”#” onclick=”getRequest2();” > 非简单请求 </a>
  2. function getRequest2(){
  3. console.log(“getRequest2”);
  4. $.ajax({
  5. url:””,
  6. dataType:”JSON”,
  7. type:”POST”,
  8. data:{“name”:”Jasper”,”age”:20,”sex”:”man”},//请求的数据为JSON对象
  9. //cache:true,//表示请求可以被缓存
  10. async:true,
  11. success:function(data_response){
  12. console.log(data_response);
  13. },
  14. error:function(){
  15. alert(“error”);
  16. }
  17. });
  18. }

Controller层添加如下代码:

[java] view plain copy

  1. @RequestMapping(“getuser”)
  2. public JSONResult getUser(UserVO userVO){
  3. System.out.println(“AjaxServerController–>getUser”);
  4. System.out.println(userVO.getName());
  5. return JSONResult.ok(userVO);
  6. }

添加UserVO类:

[java] view plain copy

  1. public class UserVO {
  2. private String name;
  3. private Integer age;
  4. private String sex;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this .name = name;
  10. }
  11. public Integer getAge() {
  12. return age;
  13. }
  14. public void setAge(Integer age) {
  15. this .age = age;
  16. }
  17. public String getSex() {
  18. return sex;
  19. }
  20. public void setSex(String sex) {
  21. this .sex = sex;
  22. }
  23. }

浏览器访问,点击非简单请求链接可以看到如下信息:

可以看到浏览器发起了两次getUser请求,第一次getUser请求是预检命令,当预检命令通过后,再发第二次请求然后服务器进行响应。而简单请求只有一次请求,如下图:

那么问题又出现了,如果每次发送非简单请求,浏览器都要发起两次请求岂不很影响速度?如何只让浏览器在第一次发起的非简单请求中请求两次,之后只请求一次呢(因为第一次预检命令通过之后,就没有必要在之后的每次请求都发送一次预检命令)?可以通过设置缓存!Win10浏览器已经自动将预检命令加入到缓存(从上图可知预检命令执行时间为0秒,且来自缓存)。当然,也可以通过res.addHeader(“Access-Control-Max-Age”, “3600”);告诉浏览器在一个小时内可以缓存设置的头部信息。

至此,Ajax跨域请求相关问题已全部介绍完毕,欢迎大家指出不足之处!

文章来源:智云一二三科技

文章标题:Ajax跨域问题详解

文章地址:https://www.zhihuclub.com/181831.shtml

关于作者: 智云科技

热门文章

网站地图