AJAX跨域

說明:本文部份內容均來自慕課網。@慕課網:https://www.imooc.comcss

課程介紹html

  • 什麼是AJAX跨域問題
  • 產生AJAX跨域問題的緣由
  • 解決AJAX跨域問題的思路和方法

什麼是AJAX跨域問題前端

  • 簡單來講,就是前端調用後端服務接口時
  • 若是服務接口不是同一個域,就會產生跨域問題

AJAX跨域場景java

  • 先後端分離、服務化的開發模式
  • 先後端開發獨立,前端須要大量調用後端接口的場景
  • 只要後端接口不是同一個域,就會產生跨域問題
  • 跨域問題很廣泛,解決跨域問題也很重要

AJAX跨域緣由jquery

  • 瀏覽器限制:瀏覽器安全校驗限制
  • 跨域(協議、域名、端口任何一個不同都會認爲是跨域)
  • XHR(XMLHttpRequest)請求

AJAX跨域問題解決思路nginx

  • 瀏覽器:瀏覽器取下跨域校驗,實際價值不大
  • XHR:不使用XHR,使用JSONP,有不少弊端,沒法知足如今的開發要求
  • 跨域:被調用方修改支持跨域調用(指定參數);調用方修改隱藏跨域(基於代理)

編寫測試代碼web

  • 被調用方後端代碼編寫:Spring Boot
  • 調用方前端代碼編寫:Jquery
  • 引入前端Jasmine測試框架

爲何會發生產生跨域問題?

上面的圖也很清晰了,由於瀏覽器爲了安全(同源),自己就限制了。ajax

  • 當咱們發送XMLHttpRequest請求的時候,若是請求的是別的域(主機域名、端口)不一樣時,那麼就會產生跨域問題(客戶端沒法獲取服務端返回的數據)

值得注意的是:跨域的問題是發生在XMLHttpRequest請求的,也就是說,不是XMLHttpRequest請求是不會有跨域問題的spring

  • 舉個很簡單的例子:在編寫網頁的時候,<img src = www.xxxx.xxxx/ >,URL不是本域的仍是能夠正常獲取該圖片的

解決跨域問題的思路

 

環境搭建

2-1 後端項目

代碼編寫apache

1.建立名爲ajax-server的maven工程pom以下

複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.myimooc</groupId>
    <artifactId>ajax-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>ajax-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
複製代碼

2.編寫AjaxServerStart類

複製代碼
package com.myimooc.ajax.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <br>
 * 標題: 啓動類<br>
 * 描述: AJAX跨域講解後端項目<br>
 *
 * @author zc
 * @date 2018/04/18
 */
@SpringBootApplication
public class AjaxServerStart {

    public static void main(String[] args) {
        SpringApplication.run(AjaxServerStart.class, args);
    }
}
複製代碼

 

3.編寫ResultBean類

複製代碼
package com.myimooc.ajax.server.vo;

import java.io.Serializable;

/**
 * <br>
 * 標題: REST請求響應POJO類<br>
 * 描述: 封裝請求響應結果<br>
 *
 * @author zc
 * @date 2018/04/18
 */
public class ResultBean implements Serializable{

    private static final long serialVersionUID = 7867107433319736719L;

    private String data;

    public ResultBean(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}
複製代碼

 

4.編寫TestController類

複製代碼
package com.myimooc.ajax.server.controller;

import com.myimooc.ajax.server.vo.ResultBean;
import com.myimooc.ajax.server.vo.User;
import org.springframework.web.bind.annotation.*;

/**
 * <br>
 * 標題: 測試控制器<br>
 * 描述: 提供REST服務<br>
 * 使用 @CrossOrigin 註解支持跨域,能夠放到類或方法上面
 * @author zc
 * @date 2018/04/18
 */
@RestController
@RequestMapping("/test")
//@CrossOrigin
public class TestController {

    @GetMapping("/get1")
    public ResultBean get1() {
        System.out.println("TestController.get1");
        return new ResultBean("get1ok");
    }

    @PostMapping("/postJson")
    public ResultBean postJson(@RequestBody User user) {
        System.out.println("TestController.postJson");
        return new ResultBean("postJson" + user.getName());
    }

    @GetMapping("/getCookie")
    public ResultBean getCookie(@CookieValue(value = "cookie1") String cookie1) {
        System.out.println("TestController.getCookie");
        return new ResultBean("getCookie" + cookie1);
    }

    @GetMapping("/getHeader")
    public ResultBean getHeader(
            @RequestHeader("x-header1") String header1,
            @RequestHeader("x-header2") String header2) {
        System.out.println("TestController.getHeader");
        return new ResultBean("getHeader" + header1+header2);
    }
}
複製代碼

 

2-2 前端項目

代碼編寫

1.建立名爲ajax-client的maven工程pom以下

複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.myimooc</groupId>
    <artifactId>ajax-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>ajax-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jasmine</artifactId>
            <version>2.5.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
複製代碼

 

2.編寫index.html

複製代碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index</title>

    <link rel="stylesheet" type="text/css" href="/webjars/jasmine/2.5.0/jasmine.css">

    <script src="/webjars/jquery/3.3.0/jquery.min.js"></script>
    <script src="/webjars/jasmine/2.5.0/jasmine.js"></script>
    <script src="/webjars/jasmine/2.5.0/jasmine-html.js"></script>
    <script src="/webjars/jasmine/2.5.0/boot.js"></script>

</head>
<body>

<a href="#" onclick="get1()">發生get1請求</a>


<script>
    function get1() {
        $.getJSON("http://localhost:8080/test/get1").then(
            function (res) {
                console.log(res);
            }
        )
    }

    // 每個測試用例的超時時間
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
    // 請求的接口地址前綴
    var base = "http://localhost:8080/test";

    // 測試模塊
    describe("AJAX講解", function () {
        // 測試方法
        it("get1請求", function (done) {
            // 服務器返回的結果
            var result;
            $.getJSON(base + "/get1").then(
                function (res) {
                    result = res;
                }
            );

            // 因爲是異步請求,須要使用setTimeout來校驗
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"get1ok"
                });
                // 校驗完成,通知jasmine框架
                done();
            },100);
        });

        // // 測試方法
        // it("jsonp請求", function (done) {
        //     // 服務器返回的結果
        //     var result;
        //     $.ajax({
        //         url: base + "/get1",
        //         dataType: "jsonp",
        //         jsonp:"callback2",
        //         success: function (res) {
        //             result = res;
        //         }
        //     });
        //
        //     // 因爲是異步請求,須要使用setTimeout來校驗
        //     setTimeout(function () {
        //         expect(result).toEqual({
        //             "data":"get1ok"
        //         });
        //
        //         // 校驗完成,通知jasmine框架
        //         done();
        //     },100);
        // });


        // 測試方法
        it("postJson請求", function (done) {
            // 服務器返回的結果
            var result;
            $.ajax({
                url:base+"/postJson",
                type:"POST",
                contentType:"application/json;charset=utf-8",
                data:JSON.stringify({name:"testName"}),
                success:function(res){
                    result = res;
                }
            });

            // 因爲是異步請求,須要使用setTimeout來校驗
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"postJsontestName"
                });
                // 校驗完成,通知jasmine框架
                done();
            },100);
        });

        it("getCookie請求", function (done) {
            // 服務器返回的結果
            var result;
            $.ajax({
                url:base+"/getCookie",
                xhrFields:{
                    // 發送 AJAX 請求時帶上 cookie
                    withCredentials:true
                },
                success:function(res){
                    result = res;
                }
            });

            // 因爲是異步請求,須要使用setTimeout來校驗
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"getCookietestName"
                });
                // 校驗完成,通知jasmine框架
                done();
            },100);
        });

        it("getHeader請求", function (done) {
            // 服務器返回的結果
            var result;
            $.ajax({
                url:base+"/getHeader",
                headers:{
                    "x-header1":"AAA"
                },
                beforeSend:function(xhr){
                   xhr.setRequestHeader("x-header2","BBB")
                },
                success:function(res){
                    result = res;
                }
            });

            // 因爲是異步請求,須要使用setTimeout來校驗
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"getHeaderAAABBB"
                });
                // 校驗完成,通知jasmine框架
                done();
            },100);
        });
    });

</script>

</body>
</html>
複製代碼

 

3.編寫application.properties

server.port=8081

 

4.編寫AjaxClientStart類

複製代碼
package com.myimooc.ajax.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AjaxClientStart {

    public static void main(String[] args) {
        SpringApplication.run(AjaxClientStart.class, args);
    }
}
複製代碼

 

5.啓動AjaxServerStart和AjaxClientStart,並訪問http://localhost:8081,點擊發生get1請求,產生跨域問題以下

 

解決跨域

3-1 禁止檢查

Chrome瀏覽器的跨域設置

  • Windows方法
  • 參考文檔:https://www.cnblogs.com/laden...
  • 使用說明:在屬性頁面中的目標輸入框里加上:--disable-web-security --user-data-dir=C:MyChromeDevUserData
  • Mac OS方法
  • 參考文檔:http://blog.csdn.net/justinji...
  • 使用說明:用命令行打開 Google Chrome:open -a "Google Chrome" --args --disable-web-security

3-2 使用JSONP

代碼編寫

1.編寫JsonpAdvice類

複製代碼
package com.myimooc.ajax.server.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

/**
 * <br>
 * 標題: JSONP 全局處理<br>
 * 描述: 統一處理JSONP<br>
 *
 * @author zc
 * @date 2018/04/18
 */
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice{

    public JsonpAdvice() {
        // 與前端約定好回調方法名稱,默認是callback
        super("callback2");
    }
}
複製代碼

 

2.修改index.html

複製代碼
        // 測試方法
        it("jsonp請求", function (done) {
            // 服務器返回的結果
            var result;
            $.ajax({
                url: base + "/get1",
                dataType: "jsonp",
                jsonp:"callback2",
                success: function (res) {
                    result = res;
                }
            });

            // 因爲是異步請求,須要使用setTimeout來校驗
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"get1ok"
                });

                // 校驗完成,通知jasmine框架
                done();
            },100);
        });
複製代碼

 

JSONP的弊端

  • 服務器須要改動代碼支持
  • 只支持GET
  • 發送的不是XHR請求

3-3 支持跨域

常見的JavaEE架構

 

跨域解決方向

  • 被調用方解決
  • 基於支持跨域的解決思路
  • 基於Http協議關於跨域的相關規定,在響應頭裏增長指定的字段告訴瀏覽器,容許調用
  • 跨域請求是直接從瀏覽器發送到被調用方
  • 修改被調用方的Http服務器
  • 調用方解決
  • 基於隱藏跨域的解決思路
  • 跨域請求不會瀏覽器直接發送到被調用方
  • 而是從中間的Http服務器(Apache、Nginx)轉發過去
  • 修改調用方的Http服務器

被調用方支持跨域

  • 【重點】Web應用服務器(Tomcat、Netty、WebLogic或應用程序)實現
  • Http服務器(Nginx)配置實現
  • Http服務器(Apache)配置實現

使用Filter解決

編寫代碼

1.編寫CrosFilter類

複製代碼
package com.myimooc.ajax.server.config;

import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * <br>
 * 標題: 服務端解決跨域<br>
 * 描述: 使用Filter<br>
 *
 * @author zc
 * @date 2018/04/18
 */
public class CrosFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse)response;

        HttpServletRequest req = (HttpServletRequest)request;

        // 支持全部域
        String origin = req.getHeader("Origin");
        if (!StringUtils.isEmpty(origin)){
            // 支持任何域名的跨域調用 且 支持帶cookie(是被調用方域名的cookie,而不是調用方的cookie)
            res.addHeader("Access-Control-Allow-Origin",origin);
        }
        // 指定容許的域,帶cookie時,origin必須是全匹配,不能使用 *
//        res.addHeader("Access-Control-Allow-Origin","http://localhost:8081");
        // 容許全部域,但不能知足帶 cookie 的跨域請求
//        res.addHeader("Access-Control-Allow-Origin","*");

        // 支持全部自定義頭
        String headers = req.getHeader("Access-Control-Allow-Headers");
        if (!StringUtils.isEmpty(headers)){
            // 容許全部header
            res.addHeader("Access-Control-Allow-Headers",headers);
        }
        // 容許全部header
//        res.addHeader("Access-Control-Allow-Headers","*");

        // 指定容許的方法
//        res.addHeader("Access-Control-Allow-Methods","GET");
        // 容許全部方法
        res.addHeader("Access-Control-Allow-Methods","*");
        // 容許瀏覽器在一個小時內,緩存跨域訪問信息(即上面三個信息)
        res.addHeader("Access-Control-Max-Age","3600");
        // 啓用 cookie
        res.addHeader("Access-Control-Allow-Credentials","true");

        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}
複製代碼

 

2.編寫FilterConfig類

複製代碼
package com.myimooc.ajax.server.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <br>
 * 標題: 配置類<br>
 * 描述: 註冊CrosFilter<br>
 *
 * @author zc
 * @date 2018/04/18
 */
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean registrationBean(){
        FilterRegistrationBean filter = new FilterRegistrationBean();
        filter.addUrlPatterns("/*");
        filter.setFilter(new CrosFilter());
        return filter;
    }
}
複製代碼

 

3.啓動AjaxServerStart和AjaxClientStart,並訪問http://localhost:8081,跨域解決

 

簡單請求與非簡單請求

  • 簡單請求:瀏覽器先發送真正的請求後檢查
  • 請求方法:GET、HEAD、POST的一種
  • 請求header:無自定義header;Content-Type爲:text/plain、multipart/form-data、application/x-www-form-urlencoded的一種
  • 非簡單請求:瀏覽器先發預檢命令,檢查經過後,才發送真正的請求
  • 常見的有:PUT、DELETE
  • 其它條件:發送Json格式的請求、帶自定義header的請求
  • 預檢命令:瀏覽器檢測到跨域請求, 會自動發出一個OPTIONS請求, 就是所謂的預檢(preflight)請求。當預檢請求經過的時候,才發送真正的請求。

Nginx配置

  • 修改主機hosts文件增長映射本地域名:127.0.0.1 b.com(表示被調用方的域名)
  • 在conf目錄下建立vhost目錄
  • 修改nginx.conf在最後面增長一行代碼:include vhost/*.conf;
  • 在vhost目錄下建立b.com.conf
  • 啓動niginx,訪問b.com/test/get1

編寫b.com.conf

複製代碼
server{
    listen 80;
    server_name b.com;

    location /{
        proxy_pass http://localhost:8080/;

        add_header Access-Control-Allow-Methods *;
        add_header Access-Control-Max-Age 3600;
        add_header Access-Control-Allow-Credentials true;

        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Headers $http_access_control_allow_headers;

        if ($request_method = OPTIONS){
            return 200;
        }
    }
}
複製代碼

 

Apache配置

  • 修改conf/httpd.conf找到LoadModule vhost_alias_module module/mod_vhost_alias.so取消註釋
  • 修改conf/httpd.conf找到LoadModule proxy_module module/mod_ proxy.so取消註釋
  • 修改conf/httpd.conf找到LoadModule proxy_http_module module/mod_ proxy_http.so取消註釋
  • 修改conf/httpd.conf找到LoadModule headers_module module/mod_ headers.so取消註釋
  • 修改conf/httpd.conf找到LoadModule rewrite_module module/mod_ rewrite.so取消註釋
  • 修改conf/httpd.conf找到Include conf/extra/httpd-vhosts.conf取消註釋
  • 修改conf/extra/httpd-vhosts.conf在最後面增長下面的內容便可
複製代碼
<VirtualHost *:80>
    ServerName b.com
    ErrorLog "logs/b.com-error.log"
    CustomLog "logs/b.com-access.log" common
    ProxyPass / http://localhost:8080/

    # 把請求頭的origin值返回到Access-Control-Allow-Origin字段
    Header always set Access-Control-Allow-Origin "expr=%{req:origin}"

    # 把請求頭的Access-Control-Allow-Headers值返回到Access-Control-Allow-Headers字段
    Header always Access-Control-Allow-Headers "expr=%{Access-Control-Allow-Headers}"

    Header always set Access-Control-Allow-Methods "*";
    Header always set Access-Control-Max-Age "3600";
    Header always set Access-Control-Allow-Credentials ""true";

    # 處理預檢命令OPTIONS,直接返回204
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD}OPTIONS
    RewriteRule ^(.*)$"/" [R=204,L]
</VirtualHost>
複製代碼

 

Spring框架支持

  • 在類或方法上使用註解@CrossOrigin便可支持跨域

3-4 隱藏跨域

使用Nginx反向代理實現

  • 修改主機hosts文件增長映射本地域名:127.0.0.1 a.com
  • 在vhost目錄下建立a.com.conf
  • 啓動niginx,訪問a.com/ajaxserver/get1

編寫a.com.conf

複製代碼
server{
    listen 80;
    server_name a.com;

    location /{
         proxy_pass http://localhost:8081/;
    }

    location /ajaxserver{
         proxy_pass http://localhost:8080/test/;
    }

}
複製代碼

 

使用Apache反向代理實現

  • 修改conf/extra/httpd-vhosts.conf在最後面增長下面的內容便可
複製代碼
<VirtualHost *:80>
    ServerName a.com
    ErrorLog "logs/a.com-error.log"
    CustomLog "logs/a.com-access.log" common
    ProxyPass / http://localhost:8081/
    ProxyPass /ajaxserverapache http://localhost:8080/test
</VirtualHost>
複製代碼

 

課程總結

4-1 課程總結

課程總結

  • 產生緣由:主要是瀏覽器對Ajax請求的限制
  • 解決思路:JSONP、支持跨域、隱藏跨域
  • 核心原理:瞭解Http協議關於跨域方面的規定
  • 解決方法:使用Filter、Nginx正反向代理、Apache正反向代理、Spring框架支持

相關文章
相關標籤/搜索