【前后端】【原理】CORS解决跨域问题

发布于:2025-07-16 ⋅ 阅读:(21) ⋅ 点赞:(0)

1.浏览器为什么会有跨域问题

  • 同源策略(SOP Same origin policy):是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也是最基本的安全功能,如果缺少同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip,也非同源。
  • 跨域(Cross Origin)指浏览器不允许当前页面所在的源去请求另一个源的数据。(要理解当前页面的源是什么?就是当前页面部署的服务器路径)跨域不一定都会有跨域问题。因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。因此:跨域问题 是针对ajax的一种限制。
    在这里插入图片描述

首先第一个问题。这个请求会不会发出去?
实际上是会的,准确来说绝大部分情况下,浏览器虽然发现这个请求跨域了,任然会把这些请求都会发送出去。
等待服务端响应后,浏览器会进行一个校验,通过则可以继续执行后续的操作。否则抛出跨域错误。

2.CORS基本理念

Cross-Origin Resource Sharing
CORS是一套机制,用于浏览器校验跨域请求
它的基本理念是:
只要服务器明确表示允许,则校验通过
服务器明确拒绝没有表示,则校验不通过

所以就可以产生一个基本认识了:基于这套规则来解决跨域问题,必须要保证服务器那边是自己人。

3.CORS具体校验规则

CORS将请求分为两类: 简单请求预检请求
简单请求的校验会宽松一些,预检请求的校验会严格一些

3.1如何区分简单请求、预检请求?

  1. 简单请求:需要满足以下全部条件
    1. 请求方法为:GETHEADPOST
    2. HTTP的头信息不超出以下几种字段:
      Accept
      Accept-Language
      Content-Language
      Last-Event-ID
      Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain
    3. 其他请求字段都是使用浏览器默认的。
  2. 预检请求:非简单请求

如下代码案例,可以更直观的看出来

// 1 简单请求
fetch('https://douyin.com')
// 2 预检请求,因为修改了头部
fetch('https://douyin.com',{
	headers: {
		a: 1
	}
})
// 3 简单请求 没有修改头部
fetch('https://douyin.com',{
	method: 'POST',
	body: JSON.stringify({a:1,b:2})
})
// 4 预检请求,修改的content-type超出了3个选项之外
fetch('https://douyin.com',{
	method: 'POST',
	headers: {
		'content-type': 'application/json'
	},
	body: JSON.stringify({a:1,b:2})
})
// 5 预检请求
// 如果使用了一些第三方库比如axios
// 那么这些请求就是预检请求了,因为这些第三方库往往会在内部有一些处理
// 比如axios 在发现参数是object时就会自动加上 content-type:application/json
axios.post('https://douyin.com',{a:1,b:2})

在浏览器中也可以直接看出来
带上preflight的就是预检请求,预检请求会在请求之前先发送一个 OPTIONS 方法的请求。后面会细说在这里插入图片描述

3.2简单请求校验规则

当浏览器发现发起的ajax请求是简单请求时,会在请求头中携带一个字段:Origin.
Origin中会指出当前请求属于哪个域(协议+域名+端口)和Request URL进行对比。服务会根据这个值决定是否允许其跨域。
在这里插入图片描述
如果服务器允许跨域,需要在返回的响应头中携带下面信息:
Access-Control-Allow-Origin: http://manage.leyou.com
Access-Control-Allow-Credentials: true
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*(代表任意域名)
Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true。
要想操作cookie,需要满足3个条件

  • 服务的响应头中需要携带Access-Control-Allow-Credentials并且为true。
  • 浏览器发起ajax需要指定withCredentials 为true
  • 响应头中的Access-Control-Allow-Origin一定不能为*,必须是指定的域名

3.3预检请求校验规则

在正式通信之前,浏览器会先发送一次方法为OPTIONS的预检请求。浏览器先询问服务器是否允许跨域得到肯定的答复,浏览器才会向服务器发送正式请求,否则抛出CORS异常。

  1. 浏览器会携带很多信息给服务器
    • Origin: 请求源是哪里
    • 请求的方法是什么
    • 修改了哪些header字段
  2. 然后服务端需要响应:
    • 允许哪些源访问
    • 允许哪些请求方法
    • 允许修改哪些header字段
    • 还可以带上一个缓存 Max-Age: 86400秒内服务器的规则都是一样的,不用再发送预检请求了。
  3. 预检请求通过后,浏览器才会发送正式请求。
    在这里插入图片描述
  • 预检请求头字段:
    • Access-Control-Request-Method:接下来会用到的请求方式,比如PUT
    • Access-Control-Request-Headers:会额外用到的头信息
  • 预检请求的响应:
    • Access-Control-Allow-Methods:允许访问的方式
    • Access-Control-Allow-Headers:允许携带的头
    • Access-Control-Max-Age:本次许可的有效时长,单位是秒,过期之前的ajax请求就无需再次进行预检了

4.java后端使用CORS解决跨域问题

4.1 WebMvcConfigurer#addCorsMappings方法

注意注意注意:addMapping()配置应用路径(server.servlet.context-path)是不能进行拦截的,拦截的而是Controller配置确切的路径。一般配置/**即可,即拦截所有Controller配置的路径

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 CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 拦截的路径
        // 注意注意注意:addMapping()配置应用路径(server.servlet.context-path)是不能进行拦            
        // 截的,拦截的而是Controller配置确切的路径
        // 一般配置/**即可,即拦截所有Controller配置的路径
        registry.addMapping("/**")
                // 允许跨域的域名,*:代表所有。允许携带cookie不能为*
                .allowedOrigins("*")
                // 允许跨域的请求方法
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                // 是否允许跨域携带cookie,为true允许跨域的域名需要指定,不能为*
                .allowCredentials(false)
                // 本次许可的有效时间,单位秒,过期之前的ajax请求就无需再次进行预检
                // 默认是1800s,此处设置1h
                .maxAge(3600)
                // 允许跨域携带的头信息,*代表所有头。可以添加多个
                .allowedHeaders("*");
    }
}

4.2 自定义Filter解决跨域

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@Component
@WebFilter(urlPatterns = { "/*" }, filterName = "headerFilter")
public class HeaderFilter implements Filter {
 
    private static final Logger logger = LoggerFactory.getLogger(HeaderFilter.class);
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
       HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
        // 解决跨域访问报错
        // 允许跨域的域名,*:代表所有域名
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许跨域请求的方法
        httpServletResponse.setHeader("Access-Control-Allow-Methods",  "POST, PUT, GET, OPTIONS, DELETE");
        // 本次许可的有效时间,单位秒,过期之前的ajax请求就无需再次进行预检啦
        // 默认是1800s,此处设置1h
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        // 允许的响应头
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
        // 支持HTTP 1.1.
        httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        // 支持HTTP 1.0. response.setHeader("Expires", "0");
        httpServletResponse.setHeader("Pragma", "no-cache");
        // 编码
        httpServletResponse.setCharacterEncoding("UTF-8");
        // 放行
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("-----------------cross origin filter start-------------------");
    }
 
    @Override
    public void destroy() {
        logger.info("-----------------cross origin filter end-------------------");
    }
}

4.3 @CrossOrigin注解

@CorssOrigin一个注解轻松解决,可以用在类上和方法。

后续小问题

  1. 跨域上传图片没有问题,但是提交普通表单却遇到了跨域问题。可能的原因是什么?
    可能的原因:
    上传图片使用的请求头是Content-Type: multipart/form-data 所以上传图片是一个简单请求。
    提交表单的时候请求头是 Content-Type: application/json 是一个预检请求,
    可能是服务器只处理了简单请求的校验。没有处理预检请求的校验。

网站公告

今日签到

点亮在社区的每一天
去签到