Ajax跨域

# Ajax跨域## 什么是跨域问题

编写测试代码

创建springboot项目
2019-04-01_22-01.png

//Controller restapi
package top.majia.cross_domain;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * majia
 * 19-4-1
 */
@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/get1")
    private ResultBean get1(){
        System.out.println("TestController.get1()");
        return new ResultBean("get1 ok");
    }
}
package top.majia.cross_domain;

/**
 * majia
 * 19-4-1
 */
public class ResultBean {
    private  String data;

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

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}
  1. 调用方前台代码
index页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.3.1.min.js"/>
</head>
<!--majia  19-4-1-->
<body>
<a href="#" onclick="get1()">发出get1()请求</a>
<script>
    function get1() {
        $.getJSON("http://localhost:8080/test/get1").then(
            function (value) { console.log(value) }
        );
    }
</script>
</body>
</html>

application.yml配置
server:
  port: 8081

2019-04-01_23-06.png

  1. 引入前台Jasmine测试框架(javascript测试框架放在static文件夹下)

    2019-04-02_17-13.png

     <link rel="stylesheet" type="text/css" href="jasmine-3.3.0/jasmine.css"/>
    <script src="jasmine-3.3.0/jasmine.js"></script>
       <script src="jasmine-3.3.0/jasmine-html.js"></script>
    <script src="jasmine-3.3.0/boot.js"></script> 
//每一个测试用例的超时时间                                                                                            
    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 (jsonobj) {                                           
                result = jsonobj;                                                                         
            });                                                                                           
            //    由于是异步请求,需要使用timeout来校验                                                                  
            setTimeout(function () {                                                                      
                expect(result).toEqual({                                                                  
                    "data": "get1 ok"                                                                     
                });                                                                                       
                // 测试方法校验完成通知jasimine框架                                                                   
                done();                                                                                   
            }, 100);                                                                                      
        });                                                                                               
    });                                                                                                   

测试框架效果
2019-04-02_17-14.png

为什么会发生跨域

  1. 浏览器限制,后台正常sout日志,数据正常返回,前台浏览器也是200;说明是浏览器原因造成跨域
    2019-04-02_17-18.png
  2. 跨域,协议,域名,端口任何一个错误都可能造成跨域
    2019-04-02_17-21.png
  3. XHR(xmlhttpRequest-》xhr)请求;就是说如果不是这个请求及时跨域浏览器也不会发生报错;
    可以用xhr换成jsonp,现在用的少了
    2019-04-02_18-11.png

解决方案:

  • 被调用方修改支持跨域
  • 调用方修改隐藏跨域
    都能避免跨域问题报错。
  1. 浏览器限制:可以设置浏览器禁止检查,跨域问题浏览器会前台校验
    新启动一个浏览器进程,禁用掉安全检查;弹出的谷歌错误反馈弹窗关掉即可
    2019-04-02_18-14.png
    2019-04-02_18-15.png
    如图:没有跨域异常虽然请求超时失败
    2019-04-02_18-19.png

JSON解决跨域

jsonp如何解决跨域

  • jsonp是什么?
    非官方开发协议,约定callback返回,把json内容解析成javascript函数
  • 使用jsonp,服务器后台需要改动吗?需要改动,对controller增加advice切面
    2019-04-02_18-39.png
    jsonp请求与普通跨域请求的区别:
    2019-04-02_19-52.png
    1.type一个是script,一个是xhr
    2.一个请求地址里有callback参数,一个没有;callback参数是一个协议,前后台遵循,后台代码加了super("callback");点击去这个地址可以发现是一个JQUERYXXXX({"data“:”get1 ok"}) 函数;没有callback只是返回一个json数据
    2019-04-02_19-53.png
    3.content-type返回值类型一个是application/javascript,一个是application/json
    现在callback也解析成json了,浏览器只是发出警告,jsonp跨域请求报错。
    2019-04-02_19-48.png

调试动态生成的javascript

  1. 首先把jquery换成无压缩版的
  2. 找到对应的callback生成动态javascript并放到html中的jquery代码位置打断点
    谷歌开发者工具跳转到指定行:ctrl+shift+p打开run command
    删除>输入:行号回车即可。
    2019-04-02_20-22.png
    2019-04-02_20-25.png
    2019-04-02_20-26.png
    刷新网页看到生成的script脚本
    2019-04-02_20-27.png

jsonp有什么弊端

  • 服务器需要改动代码支持
  • 只支持get方法
  • 发送的不是xhr请求,xhr有异步,事件等新特性

跨域解决方向

被调用方解决:修改被调用方的http服务器(apache/nginx)

增加响应头字段三个地方可以去做
- 服务器端实现
- nginx配置
- apache配置

修改服务器端之Filter解决方案(使用拦截器增加head)

  • 浏览器限制请求先执行请求(后台接收执行了)还是先判断是否是跨域请求
  • 如何判断(如果是跨域请求:请求头会多一个origin字段-》根据这个字段跟response信息比对)
  • filter代码实现
    2019-04-02_22-21.png
package top.majia.cross_domain;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class CrossDomainApplication {

    public static void main(String[] args) {
        SpringApplication.run(CrossDomainApplication.class, args);
    }

    /***
     * 使用解决跨域的filter
     * @return
     */
    @Bean
    public FilterRegistrationBean registerFilter(){
        FilterRegistrationBean bean=new FilterRegistrationBean();
        bean.addUrlPatterns("/*");
        bean.setFilter(new CrossFilter());
        return bean;
    }
}
package top.majia.cross_domain;

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

/**
 * majia
 * 19-4-2
 */
public class CrossFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response=(HttpServletResponse)servletResponse;
        response.addHeader("Access-Control-Allow-Origin","http://localhost:8081");//与前台request的origin值相同,或者*
        response.addHeader("Access-Control-Allow-Methods","GET");//也可以*
        filterChain.doFilter(servletRequest,response);
    }
}
简单请求和非简单请求

浏览器在处理跨域问题时会先检查是否是简单请求

  • 简单请求:就先执行后判断
    常见的简单请求:

    • 方法为:GET,HEAD,POST
    • 请求header里面无自定义头ContentType以下几种:
      text/plain
      multipart/form-data
      application/x-www-form-urlencoded
  • 非简单请求:先发一个预检命令OPTIONS也可能是xhr类型,检查通过再发请求执行
    常见的非简单请求:

    • put,delete方法的ajax请求
    • 发送json格式的ajax请求
    • 带自定义头的ajax请求
      2019-04-02_22-48.png
      2019-04-02_22-51.png
      在filter的dofilter方法里加上
response.addHeader("Access-Control-Allow-Headers","Content-type");

415错误
2019-04-02_23-03.png
后台server控制台报错:

修改请求头ajax里增加这句

   beforeSend: function(request) {
                  request.setRequestHeader("Content-Type", "application/json;charset=utf-8");
              },

2019-04-02_23-16.png
400错误控制台

2019-04-02 23:00:04.398  WARN 17008 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]//请求头格式后台不支持
2019-04-02 23:12:34.441  WARN 17008 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Invalid mime type "application/json,charset=utf-8": Invalid token character ',' in token "json,charset=utf-8"]//request请求头语法错误,写错了json后面是;写成,号了
2019-04-02 23:13:11.462  WARN 17008 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `top.majia.cross_domain.User` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value (''); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `top.majia.cross_domain.User` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('')//没有有参构造
 at [Source: (PushbackInputStream); line: 1, column: 1]]

User类添加上有参和无参构造即可。

预检请求和跨域请求都执行了,但是返回expect不匹配

2019-04-02_23-46.png

成功效果:

2019-04-02_23-48.png

预检命令的缓存(当然浏览器如果disable-cache就没法缓存了)
response.addHeader("Access-Control-Max-Age","360");//单位是秒,只有第一次进行预检,并且缓存下来预检结果;缓存后即使删除上述的头信息,因为有缓存,响应头没有允许信息了,请求依旧成功
带cookie的跨域
  1. 请求缺少cookie的情况

    2019-04-03_00-15.png

    因为cookie要使用本域的cookie所以cookie1的cookie要设置在8080端口

    浏览器打开localhost:8080/test/get1,控制台输入

    document.cookie//查看cookie
    document.cookie="cookie1=xiaofeng" //设置cookie

    2019-04-03_00-21.png

    设置好cookie后刷新浏览器

    Access to XMLHttpRequest at 'http://localhost:8080/test/getCookie' from origin 'http://localhost:8081' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

    说明带cookie跨域的时候后台origin属性不能用*;所以改回http://localhost:8081

    Access to XMLHttpRequest at 'http://localhost:8080/test/getCookie' from origin 'http://localhost:8081' has been blocked by CORS policy: 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'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

    说明带cookie时后台缺少Access-Control-Allow-Credentials这个字段并且值为true

    • 发现一个问题只支持localhost:8081当前跨域请求;127.0.0.1:8081都不支持,都失败了。

      2019-04-03_00-30.png

      解决origin从前台获取

      package top.majia.cross_domain;
      
      import org.springframework.http.HttpRequest;
      
      import javax.servlet.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      /**
      * majia
      * 19-4-2
      */
      public class CrossFilter implements Filter {
       @Override
       public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
           HttpServletResponse response=(HttpServletResponse)servletResponse;
           HttpServletRequest request= (HttpServletRequest) servletRequest;
           String origin = request.getHeader("Origin");
           if(!org.springframework.util.StringUtils.isEmpty(origin)){
               response.addHeader("Access-Control-Allow-Origin",origin);
           }
      //        response.addHeader("Access-Control-Allow-Origin","http://localhost:8081");//与前台request的origin值相同,或者*
           response.addHeader("Access-Control-Allow-Methods","GET");//也可以*
           response.addHeader("Access-Control-Allow-Headers","Content-Type");
           response.addHeader("Access-Control-Allow-Credentials","true");
           response.addHeader("Access-Control-Max-Age","360");//单位是秒,只有第一次进行预检,并且缓存下来预检结果;缓存后即使删除上述的头信息,因为有缓存,响应头没有允许信息了,请求依旧成功
           filterChain.doFilter(servletRequest,response);
       }
      }
      总结
      1. 带cookie,origin头不能为*
      2. 带cookie,发送的是被调用方的域名的cookie
带自定义头的跨域
  $.ajax({
                type:"get",
                url:base+"/getHeader",
               headers:{//第一种自定义header头
                    "x-header1":"AAA"
               },
                beforeSend: function(request) {//第二种自定义头
                    request.setRequestHeader("x-header2","BBB");
                },
                success:function (json) {
                    result=json;
                }
            });

后台获取

  String headers = request.getHeader("Access-Control-Request-Headers");

Nginx解决方案

nginx虚拟主机配置文件增加vhost目录:创建b.com.conf文件,此时nginx是被调用放的nginx服务器,b.com就是被调用方

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-Origin $http_origin;
    add_header Access-Control-Allow-Headers $http_access_control_request_headers;
    if ($request_method = OPTIONS){
        return 200;
    }
    }
}

nginx载入虚拟主机配置文件:在nginx.conf里增加

include vhost/*.conf;

测试nginx配置

nginx -t

重新载入配置

nginx -s reload

这种方式前台页面的地址没有改变,后台做了跨域处理。

Apache解决方案就是httpd服务

  1. 打开虚拟主机模块(即配置httd.conf)
mod_hedaders.so
mod_proxy_http.so
mod_proxy.so
mod_vhost_alias.so
mod_rewrite.so
  1. 配置虚拟主机配置文件
    <VirtualHost *:80>
       ServerName b.com
       ErrorLog "/var/log/httpd/b.com-error_log"
       CustomLog "/var/log/httpd/b.com-access_log" common
     # / 前后都有空格,表示网站的跟路径
       ProxyPass / http://localhost:8080/
       #把请求头的otigin值返回到Access-Control-Allow-Origin字段,header模块要打开
       Header always set Access-Control-Allow-Origin "expr=%{req:origin}"
       Header always set Access-Control-Allow-Headers "expr=%{req:Access-Control-Request-Headers}"
       Header always set Access-Control-Allow-Methods "*"
       Header always set Access-Control-Allow-Credentials "true"
       Header always set Access-Control-Max-Age "3600"
      #处理预检命令OPTIONS,直接返回204,所以要打开Rewrite模块
       RewriteEngine On
    
    #语法 RewriteCond TestString CondPattern [flags]   
    
       RewriteCond %{REQUEST_METHOD} OPTIONS
    
    #RewriteRule  模式匹配  替换的URL  [flags]    
    
       RewriteRule ^(.*)$ $1 [R=200,L]
    </VirtualHost>
    

apache启动问题

Permission denied: AH00072: make_sock: could not bind to address [::]:80

因为小于1024的端口只能是ROOT占用

命名request,response的header信息都在:就是跨域403无法访问:检查Cond和Rule是否写错

 #处理预检命令OPTIONS,直接返回204,所以要打开Rewrite模块
    RewriteEngine On
#语法 RewriteCond TestString CondPattern [flags]   
    RewriteCond %{REQUEST_METHOD} OPTIONS #如果method变量是Options通过
#RewriteRule  模式匹配  替换的URL  [flags]    
    RewriteRule ^(.*)$ $1 [R=200,L]

Spring框架的解决方案在方法或者类上加注解

@CrossOrigin

调用方解决:修改调用方http服务器

什么是反向代理:访问同一个域名的两个不同的url,会去向不同的服务器

调用方用自己的http服务器,让http服务器的反向代理去访问被调用的服务器。

被调用方去掉跨域注解

nginx反向代理配置

server{
    listen 80;
    server_name a.com;
    location /{
        proxy_pass http://localhost:8081/;
    }
    location /ajaxserver{
        proxy_pass http://localhost:8080/test;
    }
}
jquery-3.3.1.js:9600 GET http://ajaxserver/get1 net::ERR_NAME_NOT_RESOLVED

是因为:var base="/ajaxserver";测试地址写成了“http://ajaxserver”了,nginx把ajaxserver的地址当成非localhost:8081本域的了,所以dns解析失败。

apache反向代理

<VirtualHost *:80>
    ServerName a.com
    ErrorLog "a.com-error_log"
    CustomLog "a.com-access_log" common

    ProxyPass /ajaxserverapache http://localhost:8080/test
    ProxyPass / http://localhost:8081/
</VirtualHost>

index.html的测试前缀:var base="/ajaxserverapache";

总结:

流程图语法

tag=>type: content:>url

tag标记即框,type:是类型后面必须空格,content是堆类型的内容填充

先定义tag,在用tag1->tag2->tag3的形式串成那个图!

Created with Raphaël 2.1.4发生跨域的原因解决思路被调用方提供支持1.后台支持加filter结束调用方隐藏跨域,nginx/apache反向代理实现yesno
posted @ 2021-02-23 12:17  编程未来  阅读(92)  评论(0编辑  收藏  举报