同源策略

  • 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能
  • 同源策略限制cookie 等信息的跨源网页读取,可以保护本地用户信息
  • 同源策略限制跨域 ajax 请求,可以保护被跨域请求的服务器中数据库用户信息
  • 简单来说,限制cookie 等信息的跨源网页读取是为了保护自己的信息,而限制跨域 ajax 请求,是为了保护别人的信息。
  • 跨域请求的本质是请求别人的信息,所以能否跨域请求,是由被请求的服务器决定的。

同源定义

一个 URL 有三部分组成:协议、域名(指向主机)、端口,只有这三个完全相同的 URL 才能称之为同源。下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

URL 结果 原因
http://store.company.com/dir2/other.html 同源 只有路径不同
http://store.company.com/dir/inner/another.html 同源 只有路径不同
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是 80)
http://news.company.com/dir/other.html 失败 主机不同

源的继承

在页面中通过 about:blank 打开新的页面(<a href="https://www.csdn.net/" target="_blank">about:blank</a>)或通过使用javascript:执行脚本时(<a href="javascript:js_method();"/>url 执行的脚本会继承打开该 URL 的文档的源,因为这些类型的 URLs 没有包含源服务器的相关信息。

源的更改

满足某些限制条件的情况下,页面可以修改它的源。可以通过脚本将 document.domain 的值设置为其当前域或其当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。

例如:假设 http://store.company.com/dir/other.html 文档中的一个脚本执行以下语句:

document.domain = "company.com";

这条语句执行之后,页面将会成功地通过与 http://company.com/dir/page.html 的同源检测(通过检查的前提是http://company.com/dir/page.html 也将其 document.domain 设置为“company.com”,以表明它允许子域名通过修改document.domain 的方式与其进行通信 ,使用 document.domain 来允许子域安全访问其父域时,必须在父域和子域中设置document.domain 为相同的值)。

company.com 不能设置 document.domain 为 othercompany.com,因为它不是 company.com 的父域。

跨域资源共享CORS

跨域请求的本质是请求别人的信息,所以能否跨域请求,是由被请求的服务器决定的。

简单请求

同时满足以下条件的请求称之为简单请求:

  • 请求方法是以下三种方法之一:

    • HEAD
    • GET
    • POST
  • HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 如果设置了Content-Type,Content-Type的值只能是以下三种中的一种
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

简单请求的响应头:

  • Access-Control-Allow-Origin(必含)

    • 不可省略,否则请求按失败处理。
    • 该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"
    • 如果希望对某些origin可见,可以指定具体的一个或几个origin:"https://store.company.com,https://example.company.com"
  • Access-Control-Allow-Credentials(可选)
    • 该项标志着是否允许请求当中包含cookies信息。
    • 这一项与XMLHttpRequest对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,该项也为false。
  • Access-Control-Expose-Headers(可选)
    • 该项确定XMLHttpRequest对象当中getResponseHeader()方法所能获得的额外信息。
    • 通常情况下,getResponseHeader()方法只能获得如下的信息:
      • Cache-Control
      • Content-Language
      • Content-Type
      • Expires
      • Last-Modified
      • Pragma

我们前面提到过,同源策略是浏览器的限制,不是服务器的限制,所以对于简单请求而言,服务器实际上已经将数据返回给了浏览器,同时服务器通过设置这些response header来通知浏览器它返回的数据可以给哪些源进行使用。浏览器会对response header进行检查,判断服务器返回的数据是否能返回给当前源的执行脚本。

如果服务器返回的Access-Control-Allow-Origin包含当前源,并且请求满足Access-Control-Allow-Credentials,那浏览器就会将数据返回给请求,否则的话就会报错。对于Access-Control-Expose-Headers,只有当脚本试图读取不被允许的header时才会报错,不会影响脚本拿到server返回的数据。

  • Origin与Access-Control-Allow-Origin不匹配

    • 服务器没有设置Access-Control-Allow-Origin header

      • Access to XMLHttpRequest at 'http://localhost:8080/cors/simple?type=NO_ORIGIN' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
      • GET http://localhost:8080/cors/simple?type=NO_ORIGIN net::ERR_FAILED 200
    • 当前源不在Access-Control-Allow-Origin header指定的源中
      • Access to XMLHttpRequest at 'http://localhost:8080/cors/simple?type=DIFFERENT_ORIGIN' from origin 'http://localhost:8081' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'http://test.com' that is not equal to the supplied origin.
      • GET http://localhost:8080/cors/simple?type=DIFFERENT_ORIGIN net::ERR_FAILED 200
  • withCredentials 为true,Access-Control-Allow-Credentials为false或未设置
    • Access-Control-Allow-Credentials未设置

      • Access to XMLHttpRequest at 'http://localhost:8080/cors/simple?type=FALSE_CREDENTIAL' from origin 'http://localhost:8081' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is 'false' 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.
      • GET http://localhost:8080/cors/simple?type=FALSE_CREDENTIAL net::ERR_FAILED 200
    • Access-Control-Allow-Credentials为false
      • Access to XMLHttpRequest at 'http://localhost:8080/cors/simple?type=NO_CREDENTIAL' 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.
      • GET http://localhost:8080/cors/simple?type=NO_CREDENTIAL net::ERR_FAILED 200
  • 试图读取Access-Control-Expose-Headers未指定的header
    • 请求会返回200
    • xhr.getResponseHeader("test")会报错:Refused to get unsafe header "test"

复杂请求

非简单请求即为复杂请求。复杂请求在实际进行请求之前,需要发起预检请求。

  • 对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。
  • 服务器确认允许之后,才发起实际的 HTTP 请求。
  • 在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)

预检请求头

  • Access-Control-Request-Methodd
  • Access-Control-Request-Headers

复杂请求的响应头(预检请求和实际请求的响应头):

  • Access-Control-Allow-Origin(必含)

    • 不可省略,否则请求按失败处理。
    • 该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"
    • 如果希望对某些origin可见,可以指定具体的一个或几个origin:"https://store.company.com,https://example.company.com"
  • Access-Control-Allow-Methods(必含)
    • 对预请求当中Access-Control-Request-Method的回复,回复可以时一个逗号分隔的列表。
    • 尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。
  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含)
    • 对预请求当中Access-Control-Request-Headers的回复,以逗号分隔的列表,可以返回所有支持的头部。
    • 如果所有支持的头部一时不能完全写出来,而又不想在这一层做过多的判断,可以通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可
  • Access-Control-Allow-Credentials(可选)
    • 该项标志着是否允许请求当中包含cookies信息
  • Access-Control-Max-Age(可选)
    • 以秒为单位的缓存时间,如果预检验证通过,则会缓存指定的时间,在该时间内,再次发起该请求时,将不会发起预检请求,直接发起实际请求
    • 预请求的的发送并非免费午餐,允许时应当尽可能缓存

复杂请求与简单请求的区别

  • 对于简单请求,服务器实际上已经将结果返回给了浏览器,浏览器会根据同源策略的限制以及服务器返回的响应头的设置决定是否将结果返回给脚本。
  • 对于复杂请求,则需要首先发起一个预检请求,浏览器会根据同源策略的限制以及服务器返回的预检请求响应头的设置决定是否发起实际请求,如果预检请求没有通过,则不会发起实际请求,只有当预检请求通过时才会发起实际请求。

测试代码(spring boot project)

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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/></parent><groupId>com.jessica</groupId><artifactId>cors</artifactId><packaging>jar</packaging><version>0.0.1-SNAPSHOT</version><name>cors</name><description>spring boot project with cors example</description><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope></dependency></dependencies>
</project>

CorsApplication.java

package com.jessica;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan
public class CorsApplication {public static void main(String[] args) {SpringApplication.run(CorsApplication.class, args);}
}

CorsController.java

package com.jessica.controller;import javax.servlet.http.HttpServletResponse;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import lombok.extern.slf4j.Slf4j;@RestController
@RequestMapping("/cors")
@Slf4j
public class CorsController {@RequestMapping(path = "/test", method = RequestMethod.GET)public String test(@RequestParam(name = "type") OriginTestType type, HttpServletResponse res) {res.addHeader("test", "test-header");return "test";}}

OriginTestType.java

package com.jessica.controller;public enum OriginTestType {NO_ORIGIN, DIFFERENT_ORIGIN, NO_CREDENTIAL, FALSE_CREDENTIAL, NOT_ALLOWED_HEADER, SUCCESS;
}

CorsFilter.java

package com.jessica.filter;import java.io.IOException;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;import com.jessica.controller.OriginTestType;import lombok.extern.slf4j.Slf4j;@WebFilter(urlPatterns = { "/cors/*" })
@Slf4j
public class CorsFilter implements Filter {private static final String ACCESS_CONTROL_ALLOW_HEADERS = "content-type,cookie,test";private static final String ACCESS_CONTROL_EXPOSE_HEADERS = "test";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse res = (HttpServletResponse) response;String type = req.getParameter("type");if (type != null) {OriginTestType originTestType = OriginTestType.valueOf(type);if (OriginTestType.DIFFERENT_ORIGIN.equals(originTestType)) {res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://test.com");} else if (OriginTestType.NO_CREDENTIAL.equals(originTestType)) {res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));} else if (OriginTestType.FALSE_CREDENTIAL.equals(originTestType)) {res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "false");} else if (OriginTestType.NOT_ALLOWED_HEADER.equals(originTestType)) {res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");} else if (OriginTestType.SUCCESS.equals(originTestType)) {res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_HEADERS);res.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_EXPOSE_HEADERS);}}res.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "300");if (RequestMethod.OPTIONS.name().equals(req.getMethod())) {res.setStatus(HttpStatus.OK.value());return;}chain.doFilter(request, response);}
}

测试页面index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<div><h1>Simple Request</h1><div><span>Test no ACCESS_CONTROL_ALLOW_ORIGIN header set by server</span><button type="button" onClick="testRequest('NO_ORIGIN')">test</button></div><div><span>Test origin not allowed by ACCESS_CONTROL_ALLOW_ORIGIN header set by server</span><button type="button" onClick="testRequest('DIFFERENT_ORIGIN')">test</button></div><div><span>Test withCredentials is not set by server</span><button type="button" onClick="testRequest('NO_CREDENTIAL')">test</button></div><div><span>Test withCredentials is set to false by server</span><button type="button" onClick="testRequest('FALSE_CREDENTIAL')">test</button></div><div><span>Test get not allowed response header</span><button type="button" onClick="testRequest('NOT_ALLOWED_HEADER')">test</button></div><div><span>Test request success</span><button type="button" onClick="testRequest('SUCCESS')">test</button></div><h1>Complex Request</h1><div><span>Test no ACCESS_CONTROL_ALLOW_ORIGIN header set by server</span><button type="button" onClick="testRequest('NO_ORIGIN', true)">test</button></div><div><span>Test origin not allowed by ACCESS_CONTROL_ALLOW_ORIGIN header set by server</span><button type="button" onClick="testRequest('DIFFERENT_ORIGIN', true)">test</button></div><div><span>Test withCredentials is not set by server</span><button type="button" onClick="testRequest('NO_CREDENTIAL', true)">test</button></div><div><span>Test withCredentials is set to false by server</span><button type="button" onClick="testRequest('FALSE_CREDENTIAL', true)">test</button></div><div><span>Test get not allowed response header</span><button type="button" onClick="testRequest('NOT_ALLOWED_HEADER', true)">test</button></div><div><span>Test request success</span><button type="button" onClick="testRequest('SUCCESS', true)">test</button></div><div><span>Test request success</span><button type="button" onClick="testRequest('SUCCESS', true, 2)">test</button></div>
</div><script src="index.js"></script>
</body>
</html>

测试脚本index.js

function testRequest(type, withHeader, test) {var xhr = new XMLHttpRequest();const requestUrl = test ? `http://localhost:8080/cors/test?type=${type}&test=2`:`http://localhost:8080/cors/test?type=${type}`;xhr.open('GET', requestUrl, true);xhr.withCredentials = true;if(withHeader) {xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');xhr.setRequestHeader('test', window.location.href);}xhr.send();xhr.onreadystatechange = function() {if(this.readyState === 4) {console.log(`respose: ${xhr.responseText}`);}if(this.readyState === this.HEADERS_RECEIVED) {console.log(`test header: ${xhr.getResponseHeader("test")}`);}}
}

github

​​​​​​​GitHub - JessicaWin/cors

Spring Boot CORS跨域资源共享实现方案相关推荐

  1. CORS跨域资源共享(二):详解Spring MVC对CORS支持的相关类和API【享学Spring MVC】

    每篇一句 重构一时爽,一直重构一直爽.但出了问题火葬场 前言 上篇文章通过我模拟的跨域请求实例和结果分析,相信小伙伴们都已经80%的掌握了CORS到底是怎么一回事以及如何使用它.由于Java语言中的w ...

  2. spring MVC cors跨域实现源码解析

    spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就是跨域. sp ...

  3. spring boot+secruity 跨域问题,cookie问题解决

    spring boot+secruity 跨域问题,未带cookie,导致session失效问题解决 前端vue修改 import axios from "axios" axios ...

  4. CORS跨域资源共享漏洞

    目录 同源策略 解决跨域问题的方案 CORS CORS原理 CORS漏洞攻击流程 CORS漏洞案例演示 CORS漏洞挖掘思考 漏洞危害 漏洞修复 跨域资源共享 (CORS) 是一种浏览器机制,可以对位 ...

  5. Spring Boot解决跨域问题

    Spring Boot解决跨域问题 方法一(常用) 实现接口WebMvcConfigurer,并重写addCorsMappings(CorsRegistry registry) @Configurat ...

  6. cors跨域资源共享】同源策略和jsonp

    在执行下面那段代码的时候,我遇到了一个跨域资源共享的问题 <!doctype html> <html> <head> <meta charset=" ...

  7. Spring Boot 解决跨域问题的 3 种方案!

    以下文章来源方志朋的博客,回复"666"获面试宝典 作者:telami 来源:www.telami.cn/2019/springboot-resolve-cors/ 前后端分离大势 ...

  8. Spring Boot 解决跨域问题的 3 种方案

    来源 | r6d.cn/XTrB 前后端分离大势所趋,跨域问题更是老生常谈,随便用标题去google或百度一下,能搜出一大片解决方案,那么为啥又要写一遍呢,不急往下看. 问题背景: Same Orig ...

  9. CORS跨域资源共享 1

    1. 接口的跨域问题 (1)编写的 GET 和 POST 接口,存在一个很严重的问题:不支持跨域请求. 解决接口跨域问题的方案主要有两种: ① CORS(主流的解决方案, 推荐使用 ) ② JSONP ...

最新文章

  1. IDA-3D:基于立体视觉的自动驾驶深度感知的3D目标检测
  2. Enterprise Library 3.0 体验(3):使用配置文件的Validation Application Block
  3. Gartner:到2020年人工智能将创造出230万个工作岗位
  4. 3 Useful BookmarkLets for Debugging
  5. 在 Linux 上把 MP3 的檔名和 ID3 標簽轉為 UTF-8
  6. matlab矩阵初等变换矩阵,实验一 MATLAB基本操作及矩阵初等运算
  7. 接口之用例编写、验证
  8. WinNT/Win2000/WinXP中的远线程技术之一
  9. prop在java中_jquery中attr和prop的区别
  10. java中写sql语句的小小细节
  11. java第五周课后作业
  12. Mac大文件分包split与合并cat,加密压缩zip
  13. Linux可执行文件
  14. 计算机论文物业管理系统,物业小区管理系统 计算机专业毕业论文
  15. 电感式dcdc原理(转)
  16. 江苏开票系统安全接入服务器地址,江苏省增值税发票查询平台网址.doc
  17. 2017年统计年鉴在线阅读_我在2017年阅读的内容
  18. conda关于频道和存储包pkgs的详解(副pytorch环境安装)
  19. Auto CAD:图纸材质图例(石材、瓷砖、细木工板、钢筋混凝土、 木材、夹板、黏土砖 镜面/玻璃、软质吸音层 、钢/金属、硬质吸音层、硬隔层、基层龙骨、陶质类、涂料粉刷层)的设置之详细攻略
  20. 大数据和云计算技术周报(第102期)

热门文章

  1. 首屏渲染优化性能优化
  2. nRF52832调试
  3. A Sample Crash Log
  4. 深度学习【道路提取】:马萨诸塞州道路数据集分享
  5. poi 操作Excel 删除行内容和直接删除行(poi3.17测试可用)
  6. Arduino ESP32:测试GPIO中断功能
  7. 苹果百度手机消息推送服务器,苹果消息推送服务教程
  8. 密码学-->base64隐写
  9. “2022中国民营企业500强”榜单前十企业致力慈善事业
  10. 微信开通状态检测工具(免验证码版)运行原理