CORS

什么是CORS?

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种用于在浏览器中安全地允许跨域请求的机制。它规定了当一个网页请求访问另一个域(源)上的资源时,服务器必须显式允许这种访问。CORS通过设置特定的HTTP头部来控制哪些资源可以被哪些源访问

背景和问题

在Web应用中,浏览器出于安全考虑,默认会阻止从一个域(源)上的网页向另一个域请求资源,这被称为“同源策略”同源策略限制了从不同源加载的脚本的交互,防止了恶意网站窃取数据或执行未授权操作

然而,实际开发中经常需要跨域请求资源,例如,前端应用需要从不同的API获取数据。CORS提供了一种机制,使服务器可以明确地告知浏览器,哪些跨域请求是被允许的。

CORS的工作原理

CORS通过以下几种HTTP头部来实现跨域资源共享:

  1. Access-Control-Allow-Origin:指定哪些源可以访问资源。例如,Access-Control-Allow-Origin: http://example.com表示只有http://example.com可以访问资源。可以设置为*以允许所有源。
  2. Access-Control-Allow-Methods:指定允许哪些HTTP方法。例如,Access-Control-Allow-Methods: GET, POST, PUT
  3. Access-Control-Allow-Headers:指定哪些HTTP头部可以在实际请求中使用。例如,Access-Control-Allow-Headers: Content-Type
  4. Access-Control-Allow-Credentials:指示是否允许发送Cookie或HTTP认证信息。设置为true以允许。
  5. Access-Control-Expose-Headers:指定哪些头部可以在响应中暴露给浏览器脚本。
  6. Access-Control-Max-Age:指示结果可以被缓存多长时间(以秒为单位)。

简单请求与预检请求

CORS请求分为简单请求(Simple Requests)和预检请求(Preflight Requests)两种。

简单请求

简单请求是满足以下条件的请求:

  • 使用以下方法之一:GETHEADPOST
  • 仅使用安全的HTTP头部字段:AcceptAccept-LanguageContent-LanguageContent-Type(但Content-Type仅限于application/x-www-form-urlencodedmultipart/form-datatext/plain)。

对于简单请求,浏览器会直接发送请求,并根据响应中的CORS头部决定是否允许跨域访问。

预检请求

CORS pre-flight请求是一种以OPTIONS方法发送的HTTP请求,浏览器在执行实际的跨域请求之前发送这个请求,以了解服务器是否允许使用特定的HTTP方法和头信息进行跨域请求。这个请求由浏览器自动发起,而不是由客户端代码(JavaScript)直接触发。目的就是以确认服务器是否允许跨域请求,避免未经授权的跨域访问。如果服务器允许,该预检请求返回的响应中包含相应的CORS头信息,实际请求才会被发送。

预检请求包含以下头部:

  • Origin:请求的来源域名。

  • Access-Control-Request-Method:实际请求将使用的方法。

  • Access-Control-Request-Headers:实际请求将使用的自定义头部字段(如果有)。

如果服务器允许请求,将在响应中返回相应的CORS头部,浏览器随后才会发送实际请求。

响应中包含以下头信息:

  • Access-Control-Allow-Origin:允许访问的来源域名。
  • Access-Control-Allow-Methods:允许的HTTP方法列表。
  • Access-Control-Allow-Headers:允许的自定义头信息列表(如果有)。
  • Access-Control-Max-Age(可选):预检请求的结果可以缓存的时间。

如果预检请求的响应允许实际请求,则浏览器会继续发送实际的跨域请求。否则,实际请求会被浏览器阻止。

实例

假设一个前端应用位于http://example.com,它需要访问位于http://api.example.com/data获取资源。

预检请求

1
2
3
4
5
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: http://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header

预检响应

1
2
3
4
5
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400

实际请求

如果预检请求成功,浏览器将发送实际的跨域请求。

1
2
3
4
5
6
7
PUT /data HTTP/1.1
Host: api.example.com
Origin: http://example.com
Content-Type: application/json
X-Custom-Header: custom-value

{ "data": "example" }

CORS处理

最后再来看一下CORS处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   //是否是CORS请求,是则对其处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);

CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);

executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}

getCorsHandlerExecutionChain------>
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {

//如果是预检请求(Pre-flight Request),返回专门的PreFlightHandler处理链
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
} else {
//对于实际的CORS请求,在处理链中添加一个CORS拦截器
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}

Spring会判断当前请求是否是CORSCross-Origin Resource Sharing,跨域资源共享)请求。它通过在处理链中添加CORS相关的拦截器来处理跨域请求。

如果请求是CORS预检请求(Pre-flight Request),则通过getPreFlightHandlerExecutionChain方法返回一个专门处理预检请求的HandlerExecutionChain.

对于实际的CORS请求,通过getCorsHandlerExecutionChain方法在处理链中添加一个CORS拦截器。

CORS 拦截器

CorsInterceptor是实现跨域资源共享的核心拦截器。它会检查请求的CORS头部信息,并根据配置的CORS策略处理请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {

@Nullable
private final CorsConfiguration config;

public CorsInterceptor(@Nullable CorsConfiguration config) {
this.config = config;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return corsProcessor.processRequest(this.config, request, response);
}

@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}

综上所述,Spring处理CORS请求的完整流程如下:

  1. 请求进入Spring MVC的DispatcherServlet。
  2. DispatcherServlet通过HandlerMapping查找处理器,并生成HandlerExecutionChain。
  3. 在HandlerExecutionChain中,根据请求的CORS配置添加相应的CORS拦截器(CorsInterceptor)。
  4. 如果是预检请求,返回专门的PreFlightHandler处理链。
  5. 请求通过HandlerExecutionChain处理,CORS拦截器在处理链的过程中应用CORS规则。
  6. 最终处理器处理请求,并返回响应。

通过这种方式,Spring MVC可以灵活而有效地处理各种CORS请求。


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


CORS
https://nanchengjiumeng123.top/2024/03/22/framework/spring/Spring MVC/9.什么是CORS?/
作者
Yang Xin
发布于
2024年3月22日
许可协议