在 Web 开发里,CORS 跨域配置是前后端联调的高频环节,但 90% 的开发者都踩过同一个坑:
为了省事直接用 Access-Control-Allow-Origin: *,结果无法携带 Cookie / Token,生产环境还存在严重的安全风险。
而只配置单个域名,又满足不了多前端、多域名访问的需求(比如管理后台、H5、小程序端同时请求一个后端)。
这篇博文就教你:*如何正确配置跨域,支持多个指定域名,不使用通配符 ,兼顾安全与可用性,覆盖 Java/Go/Python/PHP/Node.js 全主流语言。
一、核心前提:为什么不能用 *,又要支持多域名?
1. 通配符 * 的致命问题
- 无法携带凭证:开启
withCredentials=true(携带 Cookie、Authorization 头)时,浏览器强制禁止Origin: *,直接报错; - 安全风险极高:任何域名都能调用你的接口,容易被非法站点盗用、攻击。
2. 多域名需求场景
- 前端域名:
https://www.xxx.com - 管理后台域名:
https://admin.xxx.com - 测试环境域名:
https://test.xxx.com - 本地开发域名:
http://localhost:3000
这些场景都需要白名单式允许跨域,而不是一刀切开放。
3. 正确实现原理(重点)
- 后端维护一个允许的域名白名单;
- 获取请求头里的
Origin(当前请求域名); - 判断
Origin是否在白名单中; - 动态返回对应域名,而不是固定 * 或单个域名。
标准响应头示例:
plaintext
Access-Control-Allow-Origin: https://www.xxx.com
二、全语言实现:支持多域名跨域(无 * 可直接复制)
所有方案均为生产可用版,支持白名单、携带 Cookie、处理 OPTIONS 预检请求。
1. Java Spring Boot(推荐全局配置)
java
运行
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 {
// 允许的跨域域名白名单
private static final String[] ALLOW_ORIGINS = {
"http://localhost:3000",
"https://www.xxx.com",
"https://admin.xxx.com",
"https://test.xxx.com"
};
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(ALLOW_ORIGINS) // 直接传入白名单数组
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true) // 允许携带Cookie
.maxAge(3600)
.allowedHeaders("*");
}
}
2. Go Gin 框架(动态判断 Origin)
go
运行
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
// 域名白名单
var allowOrigins = map[string]bool{
"http://localhost:3000": true,
"https://www.xxx.com": true,
"https://admin.xxx.com": true,
}
func CorsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
// 动态判断是否在白名单
if allowOrigins[origin] {
c.Header("Access-Control-Allow-Origin", origin)
}
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
c.Header("Access-Control-Allow-Credentials", "true")
// 处理预检请求
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(CorsMiddleware())
r.Run(":8080")
}
3. Python Flask
python
运行
from flask import Flask, request
app = Flask(__name__)
# 白名单
ALLOW_ORIGINS = {
"http://localhost:3000",
"https://www.xxx.com",
"https://admin.xxx.com"
}
@app.after_request
def handle_cors(response):
origin = request.headers.get("Origin", "")
# 匹配白名单
if origin in ALLOW_ORIGINS:
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
return response
if __name__ == '__main__':
app.run(port=8080)
4. PHP (原生 & Laravel)
原生 PHP
php
运行
<?php
$allowOrigins = [
"http://localhost:3000",
"https://www.xxx.com",
"https://admin.xxx.com"
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// 动态允许
if (in_array($origin, $allowOrigins)) {
header("Access-Control-Allow-Origin: $origin");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS");
header("Access-Control-Allow-Headers: Content-Type,Authorization");
}
// 处理预检
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit;
}
echo json_encode(["code" => 200]);
?>
Laravel 中间件
php
运行
namespace App\Http\Middleware;
use Closure;
class CorsMiddleware
{
protected $allowOrigins = [
"http://localhost:3000",
"https://www.xxx.com",
];
public function handle($request, Closure $next)
{
$origin = $request->header('Origin');
$response = $next($request);
if (in_array($origin, $this->allowOrigins)) {
$response->header('Access-Control-Allow-Origin', $origin);
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
}
return $response;
}
}
5. Node.js Express
javascript
运行
const express = require('express');
const app = express();
// 白名单
const allowOrigins = [
'http://localhost:3000',
'https://www.xxx.com',
'https://admin.xxx.com'
];
// 跨域中间件
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
}
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.listen(8080);
三、生产环境必知的 3 个关键规则
- 必须动态返回 Origin,不能写死浏览器一次只认一个域名,多域名必须用白名单 + 动态匹配。
- ** 允许携带 Cookie 时,绝对不能用 ***只要开启
Access-Control-Allow-Credentials: true,*直接失效。 - OPTIONS 预检请求必须放行自定义请求头(如 Token)、POST/JSON 请求都会触发预检,后端必须处理。
四、最佳实践总结
- 开发环境:可以用框架代理,无需配置 CORS;
- 测试 / 生产环境:一律使用域名白名单,禁止使用
*; - 多端共用后端:统一维护一份跨域白名单,新增前端只需要加域名;
- 安全优先:只给信任的域名开跨域权限。
结尾
学会这种动态白名单跨域方案,你就彻底告别:
The 'Access-Control-Allow-Origin' header contains multiple valuesCredentials flag is 'true', but 'Access-Control-Allow-Origin' is '*'
所有语言一套逻辑,生产环境安全、稳定、无坑可用。
正文完
可以使用微信扫码关注公众号(ID:xzluomor)