Ajax跨域访问

  1. 云栖社区>
  2. 博客>
  3. 正文

Ajax跨域访问

蒋固金 2016-03-27 23:04:00 浏览2096
展开阅读全文

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

URL1 URL2 说明 是否允许通信
http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允许
http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夹 允许
http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名,不同端口 不允许
http://www.a.com/a.js https://www.a.com/b.js 同一域名,不同协议 不允许
http://www.a.com/a.js http://127.0.0.1/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不同 不允许
http://www.a.com/a.js http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js http://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中进行处理,在实际应用中,这样做就很麻烦了,我们可以编写一个过滤器用于跨域访问,在这里就不贴代码了,大家可以自己思考,对该问题进行为你善解决。

网友评论

登录后评论
0/500
评论
蒋固金
+ 关注