从现在开始认识跨域

栏目: Json · 发布时间: 5年前

内容简介:同源策略(Same origin policy)是一种约定,是有在 localhost:1314 后台准备一个接口在 localhost:3000 开启一个 react 前端,并使用 axios 向后台发出请求

同源策略(Same origin policy)是一种约定,是有 Netscape 提出的一个著名的安全策略。所谓 同源 指的是 域名,协议,端口相同。同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。浏览器如果检查到资源属于非同源资源时,浏览器会在控制台中报一个异常,提示 拒绝访问

什么才算是同源?

请求方 被请求方 是否跨域 详细说明
www.heheda.com www.heheda.com/app.js 属于同源
www.heheda.com www.heheda.com/app.js 协议不同( http | https )
www.heheda.com blog.heheda.com/app.js 子域名不同( www | blog )
www.heheda.com www.hehe.com 主域名不同( heheda | hehe )
www.heheda.com:8080 www.heheda.com:8081 端口不同( 8080 | 8081 )
www.heheda.com http://103.12.13.99 域名和通过 DNS 解析的 IP 也算跨域

什么情况下 AJAX 会产生跨域( 同时满足 )?

  • 浏览器限制
  • 跨域
  • XHR (XMLHttpRequest)请求

准备工作

在 localhost:1314 后台准备一个接口

@RequestMapping(value = "testString")
    public String testString(){
        log.info("request success");
        return "testString";
    }
复制代码

在 localhost:3000 开启一个 react 前端,并使用 axios 向后台发出请求

import React, {Component} from 'react';
import Axios from 'axios'

class Test extends Component{

    constructor(){
        super();
        this.state = {
            string: ''
        }
    }
	// 在第一次渲染后调用
    componentDidMount(){
        const _this = this;

        Axios.get("http://localhost:1314/testString").then(function(response){
            _this.setState({
                string: response.data
            })
        })
    }
    render(){
        return(
            <h1>{this.state.string}</h1>
        )
    }
}

export default Test;
复制代码

此时就会出现跨域问题,但是我们的请求是成功的

从现在开始认识跨域
从现在开始认识跨域

而浏览器的控制台打出了一个错误日志,这里可以发现,跨域是浏览器前台做的处理

从现在开始认识跨域

针对浏览器的处理方法

我们让浏览器禁止同源策略来解决跨域问题,不过这是客户端的处理,存在一定的专业性,对用户很不友好。

例如 chrome 可以使用 --disable-web-security 参数来进行启动,禁止同源策略

从现在开始认识跨域
从现在开始认识跨域

这个时候再去访问 localhost:3000,成功跨域访问 后台接口

从现在开始认识跨域

针对 XHR 请求的处理方法

当请求类型是 XHR 的时候后,就会出现跨域问题

从现在开始认识跨域

这种情况下可以使用 JSONP 来解决,以下是 JSONP 的引用

JSONP(JSON with Padding)是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的 script 元素是一个例外。利用 script 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

由于 axios 对 jsonp 不太支持,这里前端将会切换请求库 fetch-jsonp,同时改造后台接口

@RequestMapping(value = "testJSONP")
    public JSONPObject testJSONP(HttpServletRequest httpServletRequest){
        String callback = httpServletRequest.getParameter("callback");
        log.info("request JSONP success");
        return new JSONPObject(callback,"testJSONP");
    }
复制代码

使用 fetch-jsonp 发送请求

import React, {Component} from 'react';
import Axios from 'axios'
import fetchJSONP from 'fetch-jsonp'

class Test extends Component{

    constructor(){
        super();
        this.state = {
            string: ''
        }
    }

    // 在第一次渲染后调用
    componentDidMount(){
        const _this = this;

        fetchJSONP("http://localhost:1314/testJSONP").then(function(response){
            return response.json()
        }).then(function(json){
            _this.setState({
                string: json
            })
        }).catch(function(error){
            console.log(error)
        })
    }
    render(){
        return(
            <h1>{this.state.string}</h1>
        )
    }
}

export default Test;
复制代码

此时发送请求就可以看到该请求不再是 XHR,而是 js

从现在开始认识跨域
从现在开始认识跨域

JSONP 请求后面会自动加上 callback,后台接收到 callback 参数,就会识别这是一个 jsonp,就会将返回值从 json 转换为 JS,至于约定的 callback 参数是可以更改的,但是需要后台进行重写 JSONP 基类进行修改。同时也需要修改 fetch-jsonp,默认是发送 callback 参数

服务端支持跨域

让你的服务器告诉浏览器支持跨域

当浏览器检测到请求的目的地和来源地非同源时,会自动在请求头加上请求的来源IP( Origin )

从现在开始认识跨域

这个服务端可以在响应头中添加支持跨域的 Origin 的信息( Access-Control-Allow-Origin )来告诉浏览器,服务端支持以下 IP 的跨域请求,不包含在响应头中 IP 来源的请求将会被视为跨域请求

从现在开始认识跨域

我们可以在后台使用 过滤器 来添加响应头信息,告诉浏览器 支持跨域的 IP 来解决跨域问题

// 该过滤器会添加响应头信息
@Component
public class CorsFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 支持前端的跨域请求
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
复制代码

可以发现此时的前端的跨域请求成功了,并且响应头多了 Access-Control-Allow-Origin 的信息

从现在开始认识跨域

这样有一个问题,就是必须要手动在服务器后台的过滤器添加允许的 IP 才能跨域请求,这样不仅添加了工作量,也是不现实,我们可以使用 response.setHeader("Access-Control-Allow-Origin", "*") 来表示匹配所有 IP

从现在开始认识跨域

理解 Options 预检命令

首先需要了解一下 简单请求非简单请求 ,如果一个请求是简单请求,则浏览器会直接发送请求让服务器执行再去判断该请求是否跨域,如果是非简单请求,则会先发送一个 Options 请求去检测该请求是否跨域,跨域通过才会发送非简单请求去服务端执行。

只要同时满足以下两大条件,就属于简单请求:

  1. 请求方法是以下三种:
    • HEAD
    • GET
    • POST
  2. HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值 application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求

后台开发一个支持 PUT 请求接口用于测试

@PutMapping(value = "testPut")
    public String testPut(){
        log.info("testPut");
        return "testPut";
    }
复制代码

前端发送请求

Axios.put("http://localhost:1314/testPut").then(function(response){
            _this.setState({
                string: response.data
            })
        })
复制代码

由于 PUT 请求不属于简单请求,这个时候浏览器会先发送一个预检命令 Options 来测试是否跨域

从现在开始认识跨域

由于服务器没有返回支持的 PUT 请求方式的响应头信息,浏览器认定该请求跨域,请求失败

从现在开始认识跨域

同样的需要在服务器端进行处理,添加支持 PUT 方法的响应头信息,对应的 Key 为 Access-Control-Allow-Methods

@Component
public class CorsFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
复制代码

此时会发现前面的 PUT 请求在预检命令 Options 通过后,发送了 PUT 请求到后台执行,最终跨域请求成功

从现在开始认识跨域

如果是HTTP的头信息包含额外信息导致请求为非简单请求,则对应需要添加的响应头信息是 Access-Control-Allow-Headers ,同时可以使用 Access-Control-Max-Age 来设置 Options 预检命令的缓存时长,在缓存期内,同一个 Origin 不会每次非简单请求都先发送预检命令

@Component
public class CorsFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        // 设置预检命令缓存时长,单位为 秒
        response.setHeader("Access-Control-Max-Age", "3600");
        // 允许自带请求头 Content-Type 的请求,这个时候就可以发送 Content-Type 为 application/json 的请求
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
复制代码

携带 Cookie 的请求需要做额外处理

在很多情况下,我们的请求是需要携带 Cookie,常见于携带权限认证用的 Token,SessionId 等,如果这个时候是跨域请求,则需要作出额外的处理:

withCredentials: true
Access-Control-Allow-Headers
Access-Control-Allow-Origin

后台开发返回 Cookie 信息接口

@GetMapping(value = "testCookie")
    public String testCookie(HttpServletRequest httpServletRequest){
        Cookie[] cookies = httpServletRequest.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("token")){
                    log.info("token : {}",cookie.getValue());
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

复制代码

后台添加 Access-Control-Allow-Headers

@Component
public class CorsFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.setHeader("Access-Control-Allow-Credentials","true");
        filterChain.doFilter(servletRequest, servletResponse);
    }
复制代码

前端添加 Cookie

从现在开始认识跨域

前端发送请求

componentDidMount(){
        const _this = this;
        const config = {
            withCredentials : true
        }
        Axios.get("http://localhost:1314/testCookie", config).then(function(response){
            _this.setState({
                string: response.data
            })
        })
    }
复制代码

如果 服务端设置 Access-Control-Allow-Origin 为 true,则会跨域请求会被拒绝

从现在开始认识跨域

Access-Control-Allow-Origin 设置为 具体地址来完成跨域请求

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.setHeader("Access-Control-Allow-Credentials","true");
        filterChain.doFilter(servletRequest, servletResponse);
    }
复制代码

此时获取 Cookie 信息成功

从现在开始认识跨域

我们使用一种 骚操作 来实现接收所有 Origin 跨域请求,可以先获取请求携带的 Origin,在添加到 Access-Control-Allow-Origin 当中

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 先获取请求头中的 Origin,在动态添加 响应头的 Access-Control-Allow-Origin
        String originHeader=((HttpServletRequest)servletRequest).getHeader("Origin");
        if(originHeader != null){
            response.setHeader("Access-Control-Allow-Origin", originHeader);
        }
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.setHeader("Access-Control-Allow-Credentials","true");
        filterChain.doFilter(servletRequest, servletResponse);
    }
复制代码

参考文献


以上所述就是小编给大家介绍的《从现在开始认识跨域》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Trading and Exchanges

Trading and Exchanges

Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00

This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器