• AJAX跨域


    说明:本文部分内容均来自慕课网。@慕课网:https://www.imooc.com

    课程介绍

    • 什么是AJAX跨域问题
    • 产生AJAX跨域问题的原因
    • 解决AJAX跨域问题的思路和方法

    什么是AJAX跨域问题

    • 简单来说,就是前端调用后端服务接口时
    • 如果服务接口不是同一个域,就会产生跨域问题

    AJAX跨域场景

    • 前后端分离、服务化的开发模式
    • 前后端开发独立,前端需要大量调用后端接口的场景
    • 只要后端接口不是同一个域,就会产生跨域问题
    • 跨域问题很普遍,解决跨域问题也很重要

    AJAX跨域原因

    • 浏览器限制:浏览器安全校验限制
    • 跨域(协议、域名、端口任何一个不一样都会认为是跨域)
    • XHR(XMLHttpRequest)请求

    AJAX跨域问题解决思路

    • 浏览器:浏览器取下跨域校验,实际价值不大
    • XHR:不使用XHR,使用JSONP,有很多弊端,无法满足现在的开发要求
    • 跨域:被调用方修改支持跨域调用(指定参数);调用方修改隐藏跨域(基于代理)

    编写测试代码

    • 被调用方后端代码编写:Spring Boot
    • 调用方前端代码编写:Jquery
    • 引入前端Jasmine测试框架

    为什么会发生产生跨域问题?

    上面的图也很清晰了,因为浏览器为了安全(同源),本身就限制了。

    • 当我们发送XMLHttpRequest请求的时候,如果请求的是别的域(主机域名、端口)不同时,那么就会产生跨域问题(客户端无法获取服务端返回的数据)

    值得注意的是:跨域的问题是发生在XMLHttpRequest请求的,也就是说,不是XMLHttpRequest请求是不会有跨域问题的

    • 举个很简单的例子:在编写网页的时候,<img src = www.xxxx.xxxx/ >,URL不是本域的还是可以正常获取该图片的

    解决跨域问题的思路

    环境搭建

    2-1 后端项目

    代码编写

    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框架支持

  • 相关阅读:
    今天一个人跑了趟香山
    周六钻胡同
    (转)C#中protected用法详解
    C# base和this
    error BK1506 : cannot open file '.\Debug\ex73View.sbr': No such file or directory
    error PRJ0003 : Error spawning 'cmd.exe'
    VS2008卸载时遇到“加载安装组件时遇到问题,取消安装” 在卸载或者升级VS2008的时候,遇到“加载安装组件时遇到问题,取消安装”的情况
    我们在建立Win32工程的时候,要选择是Win32控制台应用程序还是Win32项目,那么两者到底有什么区别呢?
    开发板重新烧写时出现ERROR: Checksum failure (expected=0x3D67E6F computed=0x3E0E0CA)
    把PC上的代码移植到WINCE上
  • 原文地址:https://www.cnblogs.com/zhangyu317/p/11230196.html
Copyright © 2020-2023  润新知