同源策略和跨域解决方案 CORS

Posted 陈皮的JavaLib

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同源策略和跨域解决方案 CORS相关的知识,希望对你有一定的参考价值。

文章目录

同源策略

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

浏览器的同源策略它会阻止读取来自不同源的资源。同源策略机制主要用于阻止恶意站点读取另一个站点的数据,让用户安全地上网。

同源策略是为了保护本地数据不被 javascript 代码获取回来的数据污染,拦截的是客户端对于响应的接收,即浏览器请求发送出去了,服务器也响应了,但是响应无法被浏览器接收。

同源指协议,域名,端口都需要相同。例如相对于 https://www.chenpi.com/index.html

  • https://www.chenpi.com/user/add:同源。
  • http://www.chenpi.com/index.html:不同源,协议不同。
  • https://www.chenpi.cn/index.html:不同源,域名不同。
  • https://www.chenpi.com:8081/index.html:不同源,端口不同。

我们打开百度的页面,然后打开控制台(按 F12),输入以下命令请求京东的首页地址。发现出现了跨域错误。

fetch("https://www.jd.com/")

通过查看请求详情,请求是正常发送出去了,而且服务端也成功响应了,但是请求结果没被浏览器接收。

因为浏览器同源策略的存在,所以才出现跨域限制问题。但有时我们不需要这种跨域限制,CORS 就是解决跨域的一种解决方案。还有另外一种跨域解决方案是 JSONP,但是这种只支持 GET 请求,比较受限。

CORS 简介

CORS(Cross-Origin Resource Sharing,跨域资源共享),是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制。

CORS 原理就是在进行访问跨域资源时,浏览器和服务器通过自定义的 HTTP 头部进行协商沟通,从而决定请求或者响应是应该成功还是失败。CORS 需要浏览器和服务器同时支持。几乎所有浏览器都支持此功能,IE 浏览器版本不能低于 IE10。

CORS 请求模型

CORS 分为两种请求模型:

  1. 简单请求:支持 head/get/post 请求,请求头信息只能是Accept,Accept-Language,Content-Language,Last-Event-ID,Content-type等标准头部,不支持自定义 header,content-type 值只支持text/plain,application/x-www-urlencoded,multipart/form-data。请求不可以携带 cookie。
  2. 非简单请求,协商模型/预检请求(Preflighted Request):除了简单请求之外,其他就是属于非简单请求。

这两种请求模型的通信过程如下:

简单请求:

  1. 浏览器发出的请求添加请求头部 Origin,标识请求页面的源信息(协议,域名,端口号),例如Origin: https://www.chenpi.com
  2. 服务器接收到请求,可检查 Origin 判断此源是否可信任。如果信任允许此源请求服务器的资源,就在响应头中返回Access-Control-Allow-Origin: https://www.chenpi.com。如果是请求的资源是公共资源可直接返回Access-Control-Allow-Origin: *表示允许所有请求源。
  3. 浏览器根据响应头部结果,决定是否接收响应数据。
  4. 默认情况下,请求不可以携带 cookie,如果需要携带,ajax 请求需要设置 xhr 属性 withCredentials 的值为 true,服务器需要设置响应头Access-Control-Allow-Credentials: true

非简单请求:

  1. 例如浏览器发出 PUT 请求,浏览器会预先发出 OPTIONS 请求,携带请求头Origin: https://www.chenpi.com,Access-Control-Request-Method: PUT,Access-Control-Request-Headers: 自定义的头部字段,多个头部以逗号分隔(可选)
  2. 服务器接收到 OPTIONS 请求,根据请求头信息决定是否响应此请求,如果信任可以在响应头中添加如下信息:
  • Access-Control-Allow-Origin:https://www.chenpi.com
  • Access-Control-Allow-Methods: PUT(允许的方法,多个方法以逗号分隔)
  • Access-Control-Allow-Headers: 允许的头部字段,多个方法以逗号分隔。
  • Access-Control-Max-Age: 预检请求(OPTIONS)可以缓存的最长时间,单位秒。
  1. 预检请求被允许之后,以后每次浏览器正常的 CORS 请求(例如 PUT),就和简单请求过程一样了。

Spring Boot 实现 CORS

显示设置头部

通过 CORS 原理可知,我们只需要在响应头部中添加相应头部信息即可解决跨域问题,如下:

package com.chenpi.controller;

import com.alibaba.fastjson.JSONObject;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description
 * @Author 陈皮
 * @Date 2022/5/1
 * @Version 1.0
 */
@RestController
@RequestMapping
public class ChenPiController 

  @GetMapping("test")
  public JSONObject test(HttpServletResponse httpServletResponse) 
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("name", "陈皮");
    jsonObject.put("age", 18);
    // 解决跨域
    httpServletResponse.setHeader("Access-Control-Allow-Origin", "https://www.baidu.com");
//    httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
    return jsonObject;
  


如果需要允许携带 cookie 信息,请求头部和响应头部都添加相应信息。注意,当添加Access-Control-Allow-Credentials: true时,Access-Control-Allow-Origin的值不能设置为星号。

package com.chenpi.controller;

import com.alibaba.fastjson.JSONObject;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description
 * @Author 陈皮
 * @Date 2022/5/1
 * @Version 1.0
 */
@RestController
@RequestMapping
public class ChenPiController 

  @GetMapping("test")
  public JSONObject test(HttpServletResponse httpServletResponse) 

    JSONObject jsonObject = new JSONObject();
    jsonObject.put("name", "陈皮");
    jsonObject.put("age", 18);
    // 解决跨域
    httpServletResponse.setHeader("Access-Control-Allow-Origin", "https://www.baidu.com");
    // 允许携带cookie,浏览器跨域使用验证:fetch('http://127.0.0.1:8080/test',mode: 'cors',credentials: 'include')
    httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");

    return jsonObject;
  


注解 @CrossOrigin

对于以上方案,如果要解决跨域的接口比较多,就需要在每个接口都写重复代码。Spring Boot 提供了跨域解决方案,使用注解@CrossOrigin

@CrossOrigin 注解可作用在单个接口上,或者作用在类上(对类下的所有接口都生效)。

package com.chenpi.controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description
 * @Author 陈皮
 * @Date 2022/5/1
 * @Version 1.0
 */
@RestController
@RequestMapping
public class ChenPiController 

  @GetMapping("test")
  @CrossOrigin
  public JSONObject test() 
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("name", "陈皮");
    jsonObject.put("age", 18);
    return jsonObject;
  

对于 CORS 通信过程用到的 HTTP 头部,@CrossOrigin 注解里面都定义了相应属性,并设置了默认值。

@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin 

	// 默认是*
	@AliasFor("origins")
	String[] value() default ;

	@AliasFor("value")
	String[] origins() default ;

	// 通配符,默认空
	String[] originPatterns() default ;

	// 默认所有自定义头部都被允许,*
	String[] allowedHeaders() default ;

	// 默认允许方法和接口定义的请求方式保持一致,例如@GetMapping("test")则值为GET
	RequestMethod[] methods() default ;

    // 默认,Access-Control-Allow-Credentials:false
	String allowCredentials() default "";

    // -1代表默认值1800秒,Access-Control-Max-Age:1800
	long maxAge() default -1;


我们可以修改默认值,并且此注解可以在类中和方法中同时定义,它们会形成互补,即对任意一个头部属性,如果在方法的注解中是默认值,则会再去类上的注解中进行判断,如下:

package com.chenpi.controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description
 * @Author 陈皮
 * @Date 2022/5/1
 * @Version 1.0
 */
@RestController
@RequestMapping
@CrossOrigin(origins = "https://www.baidu.com", "https://www.jd.com", allowCredentials = "true"
    , maxAge = 3600)
public class ChenPiController 

  @GetMapping("test")
  public JSONObject test() 
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("name", "陈皮");
    jsonObject.put("age", 18);
    return jsonObject;
  

  @GetMapping("demo")
  @CrossOrigin(origins = "https://www.taobao.com", allowedHeaders = "ChenPiHeader",
      allowCredentials = "false")
  public JSONObject demo() 
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("name", "陈皮");
    jsonObject.put("age", 20);
    return jsonObject;
  


全局配置

可以进行全局配置,这样就不需要对每个接口进行单独跨域配置,如下:

package com.chenpi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer 

  @Override
  public void addCorsMappings(CorsRegistry registry) 
    registry.addMapping("/**").allowedOrigins("https://www.baidu.com", "https://www.jd.com")
        .allowCredentials(true).allowedHeaders("chenpi-header")
        .allowedMethods("PUT", "GET", "POST", "HEAD")
        .maxAge(3600);
  


本次分享到此结束啦~~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,您的支持就是我创作最大的动力!

以上是关于同源策略和跨域解决方案 CORS的主要内容,如果未能解决你的问题,请参考以下文章

同源策略和跨域解决方案 CORS

同源策略和跨域解决方案 CORS

同源策略与跨域问题解决

同源策略和跨域资源共享(CROS)

django 跨域处理

django中同源策略和跨域解决方案