AJAX跨域问题-笔记


笔记来自学习慕课网课程:官网链接

编写前后端测试代码

源码:
ajaxclient:https://github.com/PengHongfu/ajaxclient
ajaxserver:https://github.com/PengHongfu/ajaxserver

后端springboot项目-ajaxserver

依赖文件 pom.xml

pom.xml

<?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.peng</groupId>
    <artifactId>ajaxserver</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>ajaxserver</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.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-devtools</artifactId>
            <scope>runtime</scope>
        </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>

ResulstBean.java

public class ResulstBean {

    private  String data;

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

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

TestController .java

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/get1")
    public ResulstBean get1(){
        System.out.println("get1");
        return new ResulstBean("get1 ok");
    }
}

localhost:8080/test/get1
这里写图片描述

前端springboot项目-ajaxclient

pom.xml内容与后端项目一致

这里写图片描述
application.yml

server:
  port: 8081

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<a href="#" onclick="get1()">发送get1请求</a>
<script>
    function get1() {
        $.getJSON("http://localhost:8080/test/get1").then(
            function (value) {
                console.info(value);
            }
        );
    }
</script>
</body>
</html>

这里写图片描述

这里写图片描述

Failed to load http://localhost:8080/test/get1: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8081’ is therefore not allowed access.
8080端口请求8081端口,出现跨域错误

引入Jasmine测试框架

index.html中引入依赖,编写测试用例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.bootcss.com/jasmine/3.1.0/jasmine.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jasmine/3.1.0/jasmine.js"></script>
    <script src="https://cdn.bootcss.com/jasmine/3.1.0/jasmine-html.js"></script>
    <script src="https://cdn.bootcss.com/jasmine/3.1.0/boot.js"></script>
</head>
<body>
<!--<a href="#" onclick="get1()">发送get1请求</a>-->
<script>
   /* function get1() {
        $.getJSON("http://localhost:8080/test/get1").then(
            function (value) {
                console.info(value);
            }
        );
    }*/
    //测试用例超时时间
    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 (value) {
                   result = value;
                });

                //由于是异步请求,需要使用setTimeout来校验
                setTimeout(function () {
                    expect(result).toEqual({
                        "data":"get1 ok"
                    });
                    //校验完成,通知jasmine框架
                    done();
                },100);
            });
    });
</script>
</body>
</html>

这里写图片描述

产生跨域问题的原因

  1. 浏览器原因
  2. 跨域
  3. XHR(XMLHttpRequest)请求

三个条件同时满足就会产生跨域问题

http://localhost:8080/test/get1 请求执行成功,后台打印了日志,Response成功返回了结果,说明后台处理没有问题,只是浏览器进行了拦截,而浏览器拦截了测试中的跨域XHR请求。

index.html文件中请求非XHR类型请求,浏览器不会拦截

<img src="http://localhost:8080/test/get1"/>

这里写图片描述

解决思路

这里写图片描述

  • 浏览器

    解决浏览器限制,客户端都要做改动,可行性不高

  • JSONP

    动态创建一个Script请求,弊端只能发送GET请求

  • 跨域

    针对调用方A域名调用B域名,通过代理(nginx/apache),从浏览器发出去的请求都是A域名的请求,在代理里面,把制定的URL路径转到B域名,在浏览器中,始终使用的都是A域名,同一域名,从而避免跨域问题。这是一种隐藏(避免)跨域的思路

    针对被调用方,使被调用方支持跨域,支持HTTP协议关于跨域方面的要求。例如:A域名调用B域名,返回时,B域名请求返回时,加入返回字段要求浏览器允许A域名调用。这是一种支持跨域的思路
    这里写图片描述
    这里写图片描述

JSONP

Jsonp(JSON with Padding)json 的一种”使用模式“,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

为什么我们从不同的域(网站)访问数据需要一个特殊的技术(JSONP )呢?这是因为同源策略。
同源策略,它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略。

使用JSONP之后,后台是否需要修改?

index.html中,新增测试js代码

        //测试方法
        it("JSONP请求",function (done) {
            //返回结果
            var result;
            $.ajax({
                url:base+"/get1",
                dataType:"jsonp",
                jsonp:"callback",
                success:function (json) {
                    result = json;
                }
            });
            //由于是异步请求,需要使用setTimeout来校验
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"get1 ok"
                });
                //校验完成,通知jasmine框架
                done();
            },100);
        });

出现异常,说明后台需要处理JSONP请求

这里写图片描述

Spring中的处理,新建类做切面处理。

JsonAdvice.java

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

/**
 * Spring切面处理
 * Created by PengHongfu 2018-06-22 16:02
 */
@ControllerAdvice
public class JsonAdvice extends AbstractJsonpResponseBodyAdvice {
    public JsonAdvice() {
        super("callback");
    }
}

JSONP请求:
这里写图片描述
Request URL

http://localhost:8080/test/get1?callback=jQuery33109454435204550393_1529981307727&_=1529981307728

参数_为一个随机值,防止该请求被缓存。

Response

/**/jQuery33109454435204550393_1529981307727({"data":"get1 ok"});

普通的ajax请求返回的是json格式数据,JOSNP返回的是js脚本;JSONP请求参数中有callback参数,JsonAdvice 类中约定了callback参数,会对返回结果做相应处理,返回js代码,其中callback的值作为函数名,值为返回的JSON对象

JSONP的总结

JSONP是一种非官方协议,是一种约定,约定了请求参数中如果包含指定的参数(默认为callback),则服务器则将返回内容格式从json改为js脚本形式,其中返回的函数名为callback的值,函数的参数为返回的JSON对象。

弊端:

  1. 服务器需要改动代码支持
  2. 只支持GET请求方式

被调用方解决-支持跨域

这里写图片描述

http服务器 应用服务器

Filter解决方案

服务端实现
在启动类中创建一个FilterBean

AjaxserverApplication.java

    ...
    @Bean
    public FilterRegistrationBean registrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.addUrlPatterns("/*");
        bean.setFilter(new CrosFilter());
        return bean;
    }

CrosFilter.java

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

/**
 * Created by PengHongfu 2018-06-22 16:25
 */
public class CrosFilter implements javax.servlet.Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) servletResponse;     

        res.addHeader("Access-Control-Allow-Origin","http://localhost:8081");
        res.addHeader("Access-Control-Allow-Methods","*");

        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

这里写图片描述
这里写图片描述

Access-Control-Allow-Methods:*
Access-Control-Allow-Origin:*
可以使用*,表示匹配所以的域和方法,但并不能匹配所以情况,跨域传递cookie的情况不适用。

简单请求和非简单请求

这里写图片描述

public class User {
    private String name;
}

TestController.java

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

非简单请求的预检命令
非简单请求的预检命令

Failed to load http://localhost:8080/test/postJson: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

这里写图片描述

Request Headers中询问了是否允许content-type的请求头,需要在Response-Headers中声明

res.addHeader("Access-Control-Allow-Headers","Content-Type");
//res.addHeader("Access-Control-Max-Age","3600");//预检命令缓存

这里写图片描述

带Cookie的跨域

TestController.java

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

index.html

...
it("getCookie请求",function (done) {
            //返回结果
            var result;
            $.ajax({
                type:"get",
                url: base+"/getCookie",
                xhrFields:{
                    withCredentials:true
                },
                success:function (json) {
                    result = json;
                }
            });
            //由于是异步请求,需要使用setTimeout来校验
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"getCookie peng"
                });
                //校验完成,通知jasmine框架
                done();
            },100);
        });

cookie加在被调用方
这里写图片描述

执行
这里写图片描述

传递CookieAccess-Control-Allow-Origin不能使用通配符“*”,需使用具体路径。

这里写图片描述

Failed to load http://localhost:8080/test/getCookie: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ” which must be ‘true’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:8081’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

//* 带cookie时,origin必须全匹配,不能使用* 并且要使用Allow-Credentials 为true
res.addHeader("Access-Control-Allow-Origin","http://localhost:8081");
//enable cookie
res.addHeader("Access-Control-Allow-Credentials","true");

这里写图片描述

对于Access-Control-Allow-Origin这个Response-Headers,在使用Cookie的情况下,同时匹配多个域名的跨域请求,该如何处理?
这里写图片描述

从请求头中获取Origin信息,填充到Response-Headers

 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String origin = req.getHeader("Origin");

        //支持所有跨域地址
        if(!StringUtils.isEmpty(origin)){
            res.addHeader("Access-Control-Allow-Origin",origin);
        }   
        res.addHeader("Access-Control-Allow-Methods","*");
        res.addHeader("Access-Control-Allow-Headers","Content-Type");

        res.addHeader("Access-Control-Max-Age","3600");//预检命令缓存
        //enable cookie
        res.addHeader("Access-Control-Allow-Credentials","true");
        filterChain.doFilter(servletRequest,servletResponse);
    }

带自定义头的跨域

it("getHeader请求",function (done) {
            //返回结果
            var result;
            $.ajax({
                type:"get",
                url: base+"/getHeader",
                headers:{
                    "x-header1":"AAA"
                },
                beforeSend:function(xhr){
                    xhr.setRequestHeader("x-header2","BBB")
                },
                success:function (json) {
                    result = json;
                }
            });
            //由于是异步请求,需要使用setTimeout来校验
            setTimeout(function () {
                expect(result).toEqual({
                    "data":"getHeader AAA BBB"
                });
                //校验完成,通知jasmine框架
                done();
            },100);
        });
@GetMapping("/getHeader")
    public ResulstBean getHeader(@RequestHeader(value = "x-header1") String header1,
                                 @RequestHeader(value = "x-header2") String header2){
        System.out.println("getHeader");
        return new ResulstBean("getHeader "+header1 +" "+header2);
    }

这里写图片描述
这里写图片描述

 //res.addHeader("Access-Control-Allow-Headers","Content-Type,x-header1,x-header2");

约定大于配置

 String headers = req.getHeader("Access-Control-Request-Headers");
  //支持所有跨域请求头
  if(!StringUtils.isEmpty(headers)){
            res.addHeader("Access-Control-Allow-Headers",headers);
        }

上面的请求例子都是客户端直接请求被调用方的应用服务器。
这里写图片描述

被调用方,HTTP服务器(Nginx)解决

这里写图片描述

虚拟主机:多个域名指向同一个服务器,服务器根据不同域名把请求转到不同的应用服务器,看上去有多个主机,实际上只有一个主机。

配置host

C:\Windows\System32\drivers\etc\hosts

127.0.0.1 b.com

nginx conf目录下新建vhost目录
这里写图片描述
nginx.conf文件下添加

include vhost/*.conf;

vhost目录下新建b.com.conf文件
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-Allow-Credentials true;
    add_header Access-Control-Max-Age 3600;

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

    if ($request_method = OPTIONS){
        return 200;
    }
}

屏蔽后端使用过滤器
这里写图片描述

修改请求地址
这里写图片描述
启动nginx
这里写图片描述

nginx.exe -s reload重新加载配置

这里写图片描述
这里写图片描述
这里写图片描述

被调用方-Spring框架解决方案

Controller类中或方法上添加注解@CrossOrigin

@RestController
@RequestMapping("/test")
@CrossOrigin
public class TestController {
}

调用方解决跨域-隐藏跨域

这里写图片描述

反向代理:以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
访问同一个域名的两个不同URL,访问不同的两个应用服务器。

C:\Windows\System32\drivers\etc\hosts文件中新增host

a.com表示调用方的虚拟主机

127.0.0.1 b.com a.com

新建b.com.conf

server{
    listen 80;
    server_name a.com;

    location /{
        proxy_pass http://localhost:8081;
    }
    location /ajaxServer{
        proxy_pass http://localhost:8080/test;
    }
}
var base = "/ajaxServer";

启动nginx
cookie报错,添加cookie
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/Peng_Hong_fu/article/details/80804317