当前位置: 首页 > news >正文

Ajax跨域访问

最近在做一个项目,需要用到跨域访问,在这里将解决问题的过程与大家分享一下。
JavaScript出于安全方面的考虑,使用同源策略,不允许跨域调用其他页面的对象。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。
举例如下:

URL1URL2说明是否允许通信
http://www.a.com/a.jshttp://www.a.com/b.js同一域名下允许
http://www.a.com/lab/a.jshttp://www.a.com/script/b.js同一域名下不同文件夹允许
http://www.a.com:8000/a.jshttp://www.a.com/b.js同一域名,不同端口不允许
http://www.a.com/a.jshttps://www.a.com/b.js同一域名,不同协议不允许
http://www.a.com/a.jshttp://127.0.0.1/b.js域名和域名对应ip不允许
http://www.a.com/a.jshttp://script.a.com/b.js主域相同,子域不同不允许
http://www.a.com/a.jshttp://a.com/b.js同一域名,不同二级域名(同上)不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.jshttp://www.a.com/b.js不同域名不允许

对于跨域的问题,在网上也有很多解决方案,很多人都建议使用jsonp,这种方式我认为需要对服务端进行改造,代价稍高,所以在这篇博客中我们不讨论,有兴趣可以查看相关文档,今天我们主要来讨论如何使用响应头的方式来解决跨域的问题。
首先我们先来看看,跨域到底是个什么问题,我显现编写一个服务端的程序用于测试(使用Servlet),

package com.gujin.web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossDomain extends HttpServlet
{
   private static final long serialVersionUID = 1L;

   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      PrintWriter writer = response.getWriter();
      writer.write("{\"name\":\"jianggujin\"}");
      writer.flush();
      writer.close();
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      doGet(request, response);
   }

}

web.xml中进行相关配置使其生效:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>web</display-name>
    <servlet>
        <description></description>
        <display-name>CrossDomain</display-name>
        <servlet-name>CrossDomain</servlet-name>
        <servlet-class>com.gujin.web.CrossDomain</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CrossDomain</servlet-name>
        <url-pattern>/cd</url-pattern>
    </servlet-mapping>
</web-app>

配置本地host为:www.jianggujin.com,使用浏览器访问http://www.jianggujin.com/web/cd测试一下,浏览本期会显示:{"name":"jianggujin"}
接下来我们在编写一个本地的网页:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <script type="text/javascript">
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.onreadystatechange = function() {
                if (xmlHttp.readyState == 4) {
                    console.info(xmlHttp.responseText);
                }
            };
            xmlHttp.open("GET", "http://www.jianggujin.com/web/cd", true);
            xmlHttp.send();
        </script>
    </head>

    <body>
    </body>

</html>

访问页面,浏览器控制台显示如下信息(注:显示新格式可能会不同,以具体环境为准):
[Web浏览器] "XMLHttpRequest cannot load http://www.jianggujin.com/web/cd. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8020' is therefore not allowed access." /mui/crossdomain.html (0)

这就是跨域导致的问题,同样的地址我们使用浏览器可以直接访问,但是使用ajax请求的时候,由于同源策略就被限制了,请求不被允许。通过错误信息,我们也可以得到一点解决问题的消息,缺少:Access-Control-Allow-Origin头。
在w3c中对该问题也做了描述,并列举了一些头,下述内容摘自原文:

5 Syntax

This section defines the syntax of the new headers this specification introduces. It also provides a short description of the function of each header.

The resource processing model section details how resources are to use these headers in a response. Likewise, the user agent processing model section details how user agents are to use these headers.

The ABNF syntax used in this section is from HTTP/1.1. [HTTP]

HTTP/1.1 is used as ABNF basis to ensure that the new headers have equivalent parsing rules to those introduced in that specification.

HTTP/1.1 currently does not make leading OWS implied in header value definitions but that form is assumed here.

5.1 Access-Control-Allow-Origin Response Header

The Access-Control-Allow-Origin header indicates whether a resource can be shared based by returning the value of the Origin request header, “*”, or “null” in the response. ABNF:

Access-Control-Allow-Origin = “Access-Control-Allow-Origin” “:” origin-list-or-null | “*”
In practice the origin-list-or-null production is more constrained. Rather than allowing a space-separated list of origins, it is either a single origin or the string “null”.

5.2 Access-Control-Allow-Credentials Response Header

The Access-Control-Allow-Credentials header indicates whether the response to request can be exposed when the omit credentials flag is unset. When part of the response to a preflight request it indicates that the actual request can include user credentials. ABNF:

Access-Control-Allow-Credentials: “Access-Control-Allow-Credentials” “:” true
true: %x74.72.75.65 ; “true”, case-sensitive
5.3 Access-Control-Expose-Headers Response Header

The Access-Control-Expose-Headers header indicates which headers are safe to expose to the API of a CORS API specification. ABNF:

Access-Control-Expose-Headers = “Access-Control-Expose-Headers” “:” #field-name
5.4 Access-Control-Max-Age Response Header

The Access-Control-Max-Age header indicates how long the results of a preflight request can be cached in a preflight result cache. ABNF:

Access-Control-Max-Age = “Access-Control-Max-Age” “:” delta-seconds
5.5 Access-Control-Allow-Methods Response Header

The Access-Control-Allow-Methods header indicates, as part of the response to a preflight request, which methods can be used during the actual request.

The Allow header is not relevant for the purposes of the CORS protocol. ABNF:

Access-Control-Allow-Methods: “Access-Control-Allow-Methods” “:” #Method
5.6 Access-Control-Allow-Headers Response Header

The Access-Control-Allow-Headers header indicates, as part of the response to a preflight request, which header field names can be used during the actual request. ABNF:

Access-Control-Allow-Headers: “Access-Control-Allow-Headers” “:” #field-name
5.7 Origin Request Header

The Origin header indicates where the cross-origin request or preflight request originates from. [ORIGIN]

5.8 Access-Control-Request-Method Request Header

The Access-Control-Request-Method header indicates which method will be used in the actual request as part of the preflight request. ABNF:

Access-Control-Request-Method: “Access-Control-Request-Method” “:” Method
5.9 Access-Control-Request-Headers Request Header

The Access-Control-Request-Headers header indicates which headers will be used in the actual >request as part of the preflight request. ABNF:

Access-Control-Request-Headers: “Access-Control-Request-Headers” “:” #field-name

看不懂?不着急,我们急需解决问题,上面的一大串内容的意思就是提供了这些响应头用具解决ajax的跨域问题。当ajax进行跨域访问时,浏览器首先会进行一次OPTION请求判断该请求是否被循序跨域,当响应结果为允许访问时,再进行真正的请求。

下面我们对Servlet进行修改使其允许跨域:

package com.gujin.web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossDomain extends HttpServlet
{
   private static final long serialVersionUID = 1L;

   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      response.setHeader("Access-Control-Allow-Origin", "*");
      super.service(request, response);
   }

   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      PrintWriter writer = response.getWriter();
      writer.write("{\"name\":\"jianggujin\"}");
      writer.flush();
      writer.close();
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      doGet(request, response);
   }

}

再次打开网页,观察控制台输出:

这里写图片描述

通过输出,我们发现这时候已经可以正常访问了,这样我们就满足了吗?如果有Cookie之类的信息也可以正常提交吗?我们来继续测试,在此更改服务端代码:

package com.gujin.web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossDomain extends HttpServlet
{
   private static final long serialVersionUID = 1L;

   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      response.setHeader("Access-Control-Allow-Origin", "*");
      super.service(request, response);
   }

   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      // 使用Session
      request.getSession();
      Cookie[] cookies = request.getCookies();
      System.out.println("========================================");
      if (cookies != null)
      {
         for (Cookie cookie : cookies)
         {
            System.out.println(cookie.getName() + "=" + cookie.getValue());
         }
      }
      PrintWriter writer = response.getWriter();
      writer.write("{\"name\":\"jianggujin\"}");
      writer.flush();
      writer.close();
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      doGet(request, response);
   }

}

我们先用浏览器直接访问地址,访问两次,观察控制台输出:

这里写图片描述

然后我们在通过网页访问看看结果:

这里写图片描述

两次结果对比,我们可以发现,虽然我们解决了跨域,但是Cookie信息并没有提交,当然了我们也是有办法解决的:
服务端:

package com.gujin.web;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossDomain extends HttpServlet
{
   private static final long serialVersionUID = 1L;

   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      // 不可以直接使用*
      response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8020");
      response.setHeader("Access-Control-Allow-Credentials", "true");
      super.service(request, response);
   }

   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      // 使用Session
      request.getSession();
      Cookie[] cookies = request.getCookies();
      System.out.println("========================================");
      if (cookies != null)
      {
         for (Cookie cookie : cookies)
         {
            System.out.println(cookie.getName() + "=" + cookie.getValue());
         }
      }
      PrintWriter writer = response.getWriter();
      writer.write("{\"name\":\"jianggujin\"}");
      writer.flush();
      writer.close();
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      doGet(request, response);
   }

}

网页:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>跨域测试</title>
        <script type="text/javascript">
            var xmlHttp = new XMLHttpRequest();
            xmlHttp.onreadystatechange = function() {
                if (xmlHttp.readyState == 4) {
                    console.info(xmlHttp.responseText);
                }
            };
            //设置为true
            xmlHttp.withCredentials = true;
            xmlHttp.open("GET", "http://www.jianggujin.com/web/cd", true);
            xmlHttp.send();
        </script>
    </head>

    <body>
    </body>

</html>

这个时候我们再访问网页就会达到我们想要的结果了。
到这里,我们基本上就解决了跨域的问题了,在实际应用中,我们可能还会遇到其他的问题,比如请求头不允许跨域等,解决方法都是类似的,我们只要添加相应的头信息就可以了,因为测试的原因,响应头信息我是直接放在了Servlet中进行处理,在实际应用中,这样做就很麻烦了,我们可以编写一个过滤器用于跨域访问,在这里就不贴代码了,大家可以自己思考,对该问题进行为你善解决。

相关文章:

  • UILabel和Scrollview结合用,label高度自适应
  • Analytics.js简介
  • YII文件上传
  • Linux下ssh远程连接慢解决
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • 高项-3月30号培训作业
  • ThreadPoolExecutor运行机制
  • 循环创建按钮
  • python3-itertools模块和迭代器函数
  • Java字节码浅析(三)
  • swift学习笔记
  • Java静态代码分析工具——FindBugs插件的安装与使用
  • 好看的网站
  • 面试题解答
  • 大话队列
  • Angular 响应式表单之下拉框
  • C学习-枚举(九)
  • nfs客户端进程变D,延伸linux的lock
  • Python利用正则抓取网页内容保存到本地
  • VUE es6技巧写法(持续更新中~~~)
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • Vultr 教程目录
  • webgl (原生)基础入门指南【一】
  • 技术胖1-4季视频复习— (看视频笔记)
  • 开发基于以太坊智能合约的DApp
  • 类orAPI - 收藏集 - 掘金
  • 面试总结JavaScript篇
  • 实战|智能家居行业移动应用性能分析
  • 使用 QuickBI 搭建酷炫可视化分析
  • 收藏好这篇,别再只说“数据劫持”了
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • 如何用纯 CSS 创作一个货车 loader
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​学习一下,什么是预包装食品?​
  • ###C语言程序设计-----C语言学习(3)#
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (30)数组元素和与数字和的绝对差
  • (52)只出现一次的数字III
  • (安卓)跳转应用市场APP详情页的方式
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (力扣题库)跳跃游戏II(c++)
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • .gitignore文件—git忽略文件
  • .naturalWidth 和naturalHeight属性,