在当今的 Web 应用开发中,跨域访问问题是一个常见且关键的挑战。跨域访问指的是当一个网页中的脚本(通常是 JavaScript)试图从一个与当前网页的源(包括域名、协议和端口)不同的服务器获取资源或进行数据交互时所面临的限制。这种限制是由浏览器的同源策略所施加的。
同源策略的核心原则是确保只有来自相同源的网页能够相互访问和交互数据。其主要目的是保护用户的隐私和安全,防止恶意网站通过脚本获取用户在其他网站上的敏感信息。
然而,在许多现代的 Web 应用架构中,如前后端分离的模式,前端页面可能运行在一个域名(例如:www.frontend.com),而后端服务则部署在另一个域名(例如:api.backend.com)。这种架构上的分离导致了跨域访问的需求。
当发生跨域请求时,浏览器会在发送请求前进行预检(Preflight)操作。预检请求使用 OPTIONS 方法发送,以获取服务器对跨域请求的许可信息,包括允许的请求方法、请求头和其他相关权限。如果服务器的响应不符合浏览器的预期,跨域请求将被阻止。
跨域访问问题不仅影响到简单的数据获取,还可能涉及到复杂的操作,如发送 POST 请求、携带自定义请求头或需要使用 Cookie 进行身份验证等。解决跨域问题需要综合考虑安全性、性能和可扩展性等多个方面,以确保在满足业务需求的同时,不会引入新的安全风险。
项目创建及依赖配置(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>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>cors-solution</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>CORS Solution</name>
<properties>
<java.version>19</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件(application.yml)
server:
port: 8080
cors:
allowed-origins: http://your-frontend-domain.com
allowed-methods: *
allowed-headers: *
allow-credentials: true
跨域配置类(方式一:使用全局配置)
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 {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 从配置文件中读取允许的源
config.setAllowedOrigins(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-origins"))
.toString().split(",")));
// 从配置文件中读取允许的方法
config.setAllowedMethods(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-methods"))
.toString().split(",")));
// 从配置文件中读取允许的请求头
config.setAllowedHeaders(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-headers"))
.toString().split(",")));
// 从配置文件中读取是否允许携带凭证
config.setAllowCredentials(Boolean.parseBoolean(
Objects.requireNonNull(this.getProperties().get("cors.allow-credentials"))
.toString()));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
private Properties getProperties() {
return new PropertiesLoaderUtils().loadProperties(new ClassPathResource("application.yml"));
}
}
控制器类(提供测试接口)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "跨域测试成功!";
}
}
前端页面(index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
</head>
<body>
<h1>跨域测试页面</h1>
<button onclick="fetchData()">获取数据</button>
<script>
function fetchData() {
fetch('http://your-backend-domain.com/test')
.then(response => response.text())
.then(data => {
console.log(data);
})
.catch(error => console.error('错误:', error));
}
</script>
</body>
</html>
方式二:在控制器方法上使用注解
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@CrossOrigin(origins = "http://your-frontend-domain.com")
@GetMapping("/test")
public String test() {
return "跨域测试成功!";
}
}
其他方法
除了上述两种常见的方法外,还可以通过配置 WebMvcConfigurer 来解决跨域问题。
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 CorsConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 从配置文件中读取相关属性
registry.addMapping("/**")
.allowedOrigins(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-origins"))
.toString().split(",")))
.allowedMethods(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-methods"))
.toString().split(",")))
.allowedHeaders(Arrays.asList(
Objects.requireNonNull(this.getProperties().get("cors.allowed-headers"))
.toString().split(",")))
.allowCredentials(Boolean.parseBoolean(
Objects.requireNonNull(this.getProperties().get("cors.allow-credentials"))
.toString()));
}
private Properties getProperties() {
return new PropertiesLoaderUtils().loadProperties(new ClassPathResource("application.yml"));
}
}
总结
通过以上多种方式,我们可以在 SpringBoot 项目中有效地解决跨域访问问题。在实际开发中,可以根据项目的具体需求和架构选择合适的方式。全局配置适用于整个应用的所有接口,在控制器方法上使用注解则可以更灵活地控制特定接口的跨域策略,而通过配置 WebMvcConfigurer 也是一种可行的选择。