Ajax跨域访问-阿里云开发者社区

开发者社区> 蒋固金> 正文

Ajax跨域访问

简介:
+关注继续查看

最近在做一个项目,需要用到跨域访问,在这里将解决问题的过程与大家分享一下。
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中进行处理,在实际应用中,这样做就很麻烦了,我们可以编写一个过滤器用于跨域访问,在这里就不贴代码了,大家可以自己思考,对该问题进行为你善解决。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
4015 0
深入浅出JSONP--解决ajax跨域问题
取不到数据!   上周客户新买了服务器,原本在旧的服务器上放着客户的Web主页信息和一个后台程序(asp.net),在客户的主页中有一个动态显示最新消息的处理,这个处理就是通过ajax异步从那个后台程序中取得的。
835 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
3961 0
SpringBoot-12-之Ajax跨域访问全解析
一.什么是跨域呢? 1.引入: 先讲个故事:从前一个叫8080的大佬和一个8081的大佬各占一方天地,还有一个叫浏览器的大佬和8080还有8081关系都不错。
1165 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
5688 0
阿里云推出全球应用加速解决方案,快速提升跨域应用访问体验
近日,阿里云全球加速产品推出全球应用加速等多个新特性,融合云安全DDoS高防与WAF的安全防护能力,结合云解析智能DNS与GTM全局流量调度,保障企业全球应用部署的高质量、高安全、高可靠。另外邀请到在线互联网教育机构—豌豆思维,进一步分享了他们使用全球加速的经验。
1202 0
阿里云推出全球应用加速解决方案,快速提升跨域应用访问体验
近日,阿里云全球加速产品推出全球应用加速等多个新特性,融合云安全DDoS高防与WAF的安全防护能力,结合云解析智能DNS与GTM全局流量调度,保障企业全球应用部署的高质量、高安全、高可靠。另外邀请到在线互联网教育机构—豌豆思维,进一步分享了他们使用全球加速的经验。
498 0
第114天:Ajax跨域请求解决方法(二)
一、什么是跨域 我们先回顾一下域名地址的组成: http:// www . google : 8080 / script/jquery.js       http:// (协议号)                www  (子域名)              google (主域名)                8080 (端口号) script/jquery.js (请求的地址) * 当协议、子域名、主域名、端口号中任意一各不相同时,都算不同的“域”。
1094 0
+关注
115
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载