在Web端系统开发中,跨域问题是一个常见且需要妥善处理的问题。跨域(Cross-Origin)主要指的是在浏览器中,当前网页的域名、协议、端口与请求资源的域名、协议、端口不一致时,就发生了跨域。由于浏览器出于安全考虑,限制了不同域之间的资源访问,这主要是为了防止恶意网站利用用户的身份进行一些危险的操作,从而保护用户的隐私和安全。
一、同源策略简介
1.1、什么是源
在Web安全上下文中,源(Origin)是指一个URL的协议、域名和端口号的组合。这三个部分共同定义了资源的来源,浏览器会根据这些信息来判断两个资源是否属于同一源。例如,https://www.example.com:443和http://www.example.com虽然域名相同,但由于协议和端口号不同,它们被视为不同的源。
1.2、什么是同源
同源(Same-Origin)是指两个URL的协议、域名和端口号完全相同。只有当这些条件都满足时,浏览器才认为这两个资源来自同一源,从而允许它们之间的交互操作。同源策略是浏览器安全策略的一部分,用于限制不同源之间的资源访问,以防止恶意网站窃取或篡改用户数据。
1.3、是否是同源的判断
判断两个URL是否同源,需要比较它们的协议、域名和端口号是否完全相同。以下是一些示例:
- http://www.a.com/dir/page.html 与 http://www.a.com/test/index.html 同源(协议、域名、端口号均相同)
- http://www.child.a.com/test/index.html 与 http://www.a.com/test/index.html 不同源(域名不同)
- https://www.a.com/test/index.html 与 http://www.a.com/test/index.html 不同源(协议不同)
- http://www.a.com:8080/test/index.html 与 http://www.a.com/test/index.html 不同源(端口号不同)
1.4、哪些操作不受同源策略限制
尽管同源策略严格限制了跨源的资源访问,但以下操作通常不受其限制:
- 页面中的链接:用户点击链接跳转到其他网站时,不受同源策略限制。
- 重定向:页面重定向到另一个URL时,也不受同源策略限制。
- 表单提交:表单数据可以提交到与当前页面不同源的服务器。
- 跨域资源的嵌入:如<script src="...">、<img>、<link>、<iframe>等标签可以嵌入来自不同源的资源,但脚本不能通过DOM API访问这些资源的内容。
1.5、跨域的概念
跨域(Cross-Origin)是指浏览器尝试访问或操作与当前页面不同源的资源。由于同源策略的限制,跨域请求通常会被浏览器阻止,除非服务器明确允许(如通过CORS头部)。跨域问题在Web开发中非常常见,特别是在需要调用第三方API或在不同子域之间共享资源时。
1.6、跨域解决方案
为了解决跨域问题,可以采取以下几种方法:
- JSONP:一种利用<script>标签不受同源策略限制的特性实现的跨域数据交换方式。但JSONP只支持GET请求,且存在安全风险。
- CORS(Cross-Origin Resource Sharing):现代浏览器支持的跨域资源共享标准。通过服务器设置特定的HTTP响应头(如Access-Control-Allow-Origin)来允许或拒绝跨域请求。CORS是目前最常用且最安全的跨域解决方案。
- 代理服务器:将跨域请求转发到同源的代理服务器上,再由代理服务器向目标服务器发起请求,最后将响应返回给客户端。这种方法可以绕过浏览器的同源策略限制,但需要在服务器端进行额外的配置。
- 降域:通过修改document.domain属性(仅适用于子域之间的跨域),使不同子域的页面能够相互访问。但这种方法存在安全风险,且应用场景有限。
- 其他技术:如window.postMessage、WebSocket等也可以用于实现跨域通信,但它们的使用场景和限制条件各不相同。
二、CORS 简介
CORS(Cross-Origin Resource Sharing,跨源资源共享)是由W3C提出的一种机制,旨在解决浏览器同源策略(Same-Origin Policy)的限制,允许网页从不同的源(协议+域名+端口)加载资源。CORS通过服务器设置特定的HTTP头部信息,来告诉浏览器哪些跨域请求是被允许的,从而实现了安全的跨域通信。
2.1、CORS 的核心思想
- 不破坏既有规则:CORS在保持浏览器同源策略的基础上,提供了一种机制来允许跨域请求。
- 服务器控制:CORS的实现完全依赖于服务器端的配置,服务器通过发送特定的HTTP头部来告知浏览器哪些跨域请求是被允许的。
2.2、CORS 请求分类
CORS将跨域请求分为两类:简单请求(Simple Requests)和非简单请求(Preflighted Requests)。
1. 简单请求
简单请求是指那些满足以下条件的HTTP请求:
- 请求方法只能是GET、HEAD或POST。
- 对于POST方法,Content-Type的值只能是application/x-www-form-urlencoded、multipart/form-data或text/plain。
- 请求中的HTTP头信息不能包含除上述简单请求允许字段外的其他自定义字段。
简单请求在发送时,浏览器会自动在请求头中添加Origin字段,表明请求的来源。服务器根据Origin字段的值判断是否允许该跨域请求。如果允许,服务器会在响应头中添加Access-Control-Allow-Origin字段,并可能包含其他CORS相关的字段,如Access-Control-Allow-Credentials、Access-Control-Expose-Headers等。
2. 非简单请求
对于不满足简单请求条件的跨域请求,浏览器会先发送一个OPTIONS请求作为预检请求(Preflight Request),以询问服务器是否允许该跨域请求。预检请求会包含以下CORS相关的HTTP头信息:
- Origin:表明请求的来源。
- Access-Control-Request-Method:实际请求将使用的HTTP方法。
- Access-Control-Request-Headers:实际请求将携带的自定义HTTP头信息字段。
服务器收到预检请求后,会检查Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,以决定是否允许该跨域请求。如果允许,服务器会在响应头中添加以下CORS相关的字段:
- Access-Control-Allow-Origin:表明允许哪个源的请求。
- Access-Control-Allow-Methods:表明允许哪些HTTP方法。
- Access-Control-Allow-Headers:表明允许哪些自定义HTTP头信息字段。
- Access-Control-Allow-Credentials:表明是否允许发送Cookie。
- Access-Control-Max-Age:表明预检请求的有效期(单位为秒),在有效期内,浏览器不会重复发送预检请求。
当预检请求通过后,浏览器才会发送实际的跨域请求。
三、SpringBoot配置Cors解决跨域问题
在Spring Boot中处理跨域资源共享(CORS)问题,可以通过几种方式来实现。这里主要介绍两种常见的方法:使用@CrossOrigin注解和配置全局CORS。
1. 使用@CrossOrigin注解
在Spring Boot中@CrossOrigin注解是一个非常有用的工具,用于处理跨域资源共享(CORS)问题。这个注解可以被放置在类级别或方法级别,以控制哪些跨域请求被允许。它提供了灵活的CORS支持,允许你指定哪些源(origins)、HTTP方法、头部(headers)和是否允许发送Cookie等。
下面我将详细解释@CrossOrigin注解及其各个属性的含义,并展示如何将它们应用于类和方法。
属性说明
- origins:允许访问该资源的源列表。可以使用通配符*来允许所有源,或者使用具体的URL列表。
- methods:允许的HTTP方法列表,如GET、POST、PUT、DELETE等。默认允许所有方法。
- allowedHeaders:允许的HTTP头部列表。可以使用*来允许所有头部。
- exposedHeaders:浏览器可以访问的响应头部列表。这些头部通常由后端设置,但浏览器出于安全考虑可能默认不暴露给前端。
- allowCredentials:是否允许发送Cookie。默认情况下,CORS请求不会发送Cookie。当设置为true时,允许发送Cookie,但此时origins不能设置为*,必须明确指定具体的源。
- maxAge:预检请求(preflight request)的缓存时间,单位为秒。预检请求是浏览器在发送实际请求之前发送的一种请求,用于检查服务器是否允许跨域请求。设置这个值可以减少预检请求的频率,提高性能。
类级别使用
当@CrossOrigin注解被放置在类上时,它会影响该类中所有方法的CORS设置。
@RestController @CrossOrigin(origins = "http://example.com", maxAge = 3600) public class MyController { @GetMapping("/greeting") public String greeting() { return "Hello, World!"; } // 这个方法也会继承类级别的CORS设置 @PostMapping("/postGreeting") public String postGreeting() { return "Hello, POST World!"; } }
方法级别使用
@RestController public class MyController { @CrossOrigin(origins = "http://specific.com", allowCredentials = "true") @GetMapping("/secureGreeting") public String secureGreeting() { return "Secure Hello, World!"; } // 这个方法不会继承任何CORS设置,因为它没有@CrossOrigin注解 @GetMapping("/noCorsGreeting") public String noCorsGreeting() { return "Hello, No CORS!"; } }
你也可以在方法级别使用@CrossOrigin注解,以覆盖类级别的设置或仅为该方法提供CORS支持。
在上面的例子中,secureGreeting方法通过@CrossOrigin注解明确指定了允许来自http://specific.com的跨域请求,并允许发送Cookie。而noCorsGreeting方法则没有CORS支持,因此它不能响应来自不同源的跨域请求。
通过这种方式,你可以非常灵活地控制你的Spring Boot应用的CORS策略,以满足不同的安全和性能需求。
2. 配置全局CORS
如果你希望为你的整个应用设置统一的CORS策略,而不是在每个控制器或方法上单独设置,那么全局CORS配置是一个更好的选择。Spring Boot允许你通过添加一个CORS配置类来实现这一点。
方式一:通过CorsFilterBean配置CORS。
步骤细节:
- 定义CORS配置:
首先,我们定义一个私有方法(如buildConfig),用于构建并返回一个CorsConfiguration对象。在这个对象中,我们设置了允许的源(allowedOrigins)、允许的HTTP方法(allowedMethods)、允许的头部(allowedHeaders)以及是否允许发送Cookie(setAllowCredentials)。 - 注册CORS配置:
接着,我们创建一个CorsFilter的Bean(如corsFilter方法)。在这个方法中,我们首先创建一个UrlBasedCorsConfigurationSource对象,这个对象用于将特定的CORS配置与特定的URL模式关联起来。然后,我们使用registerCorsConfiguration方法将之前构建的CORS配置应用到所有URL模式(/**)上。 - 整合到Spring Boot应用:
最后,由于CorsFilter的Bean是在一个带有@Configuration注解的类中定义的,因此Spring Boot会自动检测到它,并将其注册到应用的上下文中。这样,每当有HTTP请求到达时,CorsFilter就会根据配置的CORS策略来检查并处理这些请求。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); // 允许任何域名使用 corsConfiguration.addAllowedHeader("*"); // 允许任何头 corsConfiguration.addAllowedMethod("*"); // 允许任何方法(post、get等) corsConfiguration.setAllowCredentials(true); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 对接口配置跨域设置 return new CorsFilter(source); } }
方式二:通过实现WebMvcConfigurer接口配置CORS
我们通过实现WebMvcConfigurer接口并重写addCorsMappings方法来配置CORS。这种方式利用了Spring MVC的自动配置机制,使得CORS配置更加简洁和直观。
步骤细节:
- 实现WebMvcConfigurer接口:
首先,我们创建一个配置类(如GlobalCorsConfig),并让它实现WebMvcConfigurer接口。 - 重写addCorsMappings方法:
然后,我们在这个配置类中重写addCorsMappings方法。在这个方法中,我们使用CorsRegistry对象来定义CORS策略。我们通过调用addMapping方法来指定哪些URL模式应该应用这些策略,并通过链式调用allowedOrigins、allowedMethods、allowedHeaders、allowCredentials和maxAge等方法来设置具体的CORS规则。 - 整合到Spring Boot应用:
由于这个配置类被@Configuration注解标记,Spring Boot会自动检测到它,并在启动时调用addCorsMappings方法来配置CORS。
import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration //加配置注解可以扫描到 public class WebConfig implements WebMvcConfigurer{ //跨域请求配置 @Override public void addCorsMappings(CorsRegistry registry) { WebMvcConfigurer.super.addCorsMappings(registry); registry.addMapping("/**")// 对接口配置跨域设置 .allowedHeaders("*")// 允许任何头 .allowedMethods("POST","GET")// 允许方法(post、get等) .allowedOrigins("*")// 允许任何域名使用 .allowCredentials(true); } }
总结
- 使用@CrossOrigin注解可以快速地为单个控制器或方法启用CORS,适用于简单的CORS需求。
- 全局CORS配置,例如通过实现WebMvcConfigurer接口并重写addCorsMappings方法来实现,适用于需要为整个应用设置统一CORS策略的场景。这种方式更加灵活,能够更好地满足复杂的CORS需求。