跨域请求实现方案

跨域(Cross-Origin)指的是从一个源(Origin)向另一个不同源发起的 HTTP 请求。根据浏览器的同源策略(Same-Origin Policy),一个源只能访问同源的资源,跨域访问资源时会受到限制。理解跨域需要了解几个概念:

同源策略

同源策略是一种浏览器安全机制,用于防止一个源的恶意脚本访问另一个源的敏感数据。一个源由以下三部分组成:

  1. 协议(Scheme):如 httphttps

  2. 域名(Host):如 example.com

  3. 端口(Port):如 80443

两个 URL 必须同时满足协议、域名和端口相同,才被认为是同源。

什么是跨域

跨域指的是协议、域名或端口任意一个不同的情况下,从一个网页向另一个不同源的服务器发起的 HTTP 请求。例如:

  • http://example.comhttps://example.com 发请求(协议不同)。

  • http://example.comhttp://api.example.com 发请求(域名不同)。

  • http://example.com:80http://example.com:8080 发请求(端口不同)。

常见的跨域请求

  1. AJAX 请求:前端 JavaScript 发起的异步 HTTP 请求。

  2. Web Fonts:在 CSS 中加载字体文件。

  3. WebGL 纹理:在 WebGL 上加载图像。

  4. CSS 样式表:在 CSS 中使用 @import 加载外部样式表。

  5. JavaScript 文件:使用 <script> 标签加载外部 JavaScript 文件。

  6. 图片:使用 <img> 标签加载外部图片。

解决跨域问题的方法

CORS(Cross-Origin Resource Sharing)

CORS 是一种机制,允许服务器指示浏览器允许来自不同源的请求。服务器通过设置特定的 HTTP 头来实现这一点。

示例

Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true

反向代理

可以使用 nginx 对后端服务器进行反向代理,让前端所有的请求都请求到 nginx 上,然后 nginx 进行请求的路由转发,这样不但实现了跨域请求,还可以进一步配置 nginx 以实现负载均衡。

跨域案例

一个简单的登录模块Demo,使用Spring的CORS实现跨域

首先我们编写一个前端页面,用于发送登录请求

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Form</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.login-container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.login-container h2 {
margin-bottom: 20px;
}
.login-container input {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.login-container button {
width: 100%;
padding: 10px;
background-color: #007BFF;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
}
.login-container button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Login</h2>
<form id="loginForm">
<input type="text" id="username" name="username" placeholder="Username" required>
<input type="password" id="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>

<script>
document.getElementById('loginForm').addEventListener('submit', function(event) {
event.preventDefault();

var username = document.getElementById('username').value;
var password = document.getElementById('password').value;

var data = JSON.stringify({
username: username,
password: password
});

fetch('http://localhost:8090/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: data
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
});
</script>
</body>
</html>

image-20240624174931068

现在,我们来编写后端代码,后端使用的是SpringBoot

登录接口代码如下:

@Slf4j
@RestController
@RequestMapping("/login")
public class controller {
@PostMapping
public Result<UserDTO> login(@RequestBody UserDTO userDTO) {
log.info("username:{},password:{}", userDTO.getUsername(), userDTO.getPassword());
if (userDTO.getUsername().equals("admin") && userDTO.getPassword().equals("admin"))
return Results.success(userDTO);
else return Results.failure(userDTO);
}
}

在没有设置允许跨域请求时,前端发送登录请求,会收到如下响应:

image-20240624175253175

现在,我们通过实现 WebMvcConfigurer 接口,重写其 addCorsMappings() 方法,添加允许发送跨域请求的前端地址:

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:63342")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}

重新启动后端服务,然后在前端发送登录请求,可以发现,已经实现了跨域请求:

image-20240624175547779

image-20240624175555631

image-20240624175603670