从现在开始认识跨域

栏目: 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);
    }
复制代码

参考文献


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

查看所有标签

猜你喜欢:

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

认知与设计

认知与设计

Jeff Johnson / 张一宁 / 人民邮电出版社 / 2011-9-1 / 59.00元

本书语言清晰明了,将设计准则与其核心的认知学和感知科学高度统一起来,使得设计准则更容易地在具体环境中得到应用。涵盖了交互计算机系统设计的方方面面,为交互系统设计提供了支持工程方法。不仅如此,这也是一本人类行为原理的入门书。一起来看看 《认知与设计》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具