传统"防火墙内即安全"的边界安全模型已经过时。混合办公、云原生应用、移动设备接入……安全边界早已模糊。零信任(Zero Trust)不是一句口号,而是一套可落地的架构体系。本文从原理到代码,带你 5 步在云上落地零信任。
零信任到底是什么?
零信任的核心原则只有一句话:从不信任,始终验证(Never Trust, Always Verify)。
无论请求来自内网还是外网、是员工还是合作伙伴、是笔记本还是 IoT 设备——每一次访问都必须经过身份认证、权限校验和风险评估。
传统安全 vs 零信任对比
| 维度 | 传统边界安全 | 零信任架构 |
|---|---|---|
| 信任模型 | 内网=可信,外网=不可信 | 所有访问均不可信 |
| 认证方式 | 登录一次,长期有效 | 每次请求都验证 |
| 授权粒度 | 基于角色(粗粒度) | 基于属性+上下文(细粒度) |
| 网络架构 | 城堡-护城河模型 | 微隔离+身份驱动 |
| 横向移动 | 内网可自由访问 | 严格的分段控制 |
| 适用场景 | 传统数据中心 | 混合云、远程办公、SaaS |
零信任的 5 大核心原则
- 身份即边界:一切访问以身份为起点
- 最小权限:只授予完成任务所需的最小权限
- 持续验证:不是一次性认证,而是持续评估风险
- 微隔离:将网络划分为细粒度的安全区域
- 数据为中心:保护的是数据,不是网络
第一步:搭建身份认证中心(IAP)
身份是零信任的基石。我们用 Keycloak 搭建一个统一的身份认证中心。
# docker-compose-keycloak.yml
version: '3.8'
services:
keycloak:
image: quay.io/keycloak/keycloak:24.0
container_name: keycloak
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${DB_PASSWORD:-keycloak123}
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
KC_HEALTH_ENABLED: true
KC_PROXY: edge # 如果使用反向代理
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
command: start-dev
restart: unless-stopped
postgres:
image: postgres:16-alpine
container_name: keycloak-db
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${DB_PASSWORD:-keycloak123}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
# 启动 Keycloak
docker compose -f docker-compose-keycloak.yml up -d
# 访问 http://your-server:8080
# 默认账号:admin / admin123
启动后在管理控制台完成以下配置:
- 创建 Realm(如
myapp) - 创建 Client(如
backend-api),启用 OpenID Connect - 创建用户和角色(如
developer、admin、viewer)
第二步:实现 API 网关层策略执行点(PEP)
策略执行点(Policy Enforcement Point)是零信任架构中的"门卫",负责拦截所有请求并执行策略决策。
"""
零信任 API 网关 - 策略执行点(PEP)
基于 FastAPI + JWT 实现的统一网关
"""
import jwt
import time
import httpx
import hashlib
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI(title="零信任 API 网关")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ==================== 配置 ====================
KEYCLOAK_URL = "http://localhost:8080/realms/myapp"
KEYCLOAK_JWKS_URL = f"{KEYCLOAK_URL}/protocol/openid-connect/certs"
AUDIENCE = "backend-api"
# 后端服务路由映射
BACKEND_SERVICES = {
"/api/users": "http://user-service:8001",
"/api/orders": "http://order-service:8002",
"/api/analytics": "http://analytics-service:8003",
}
# 缓存公钥
_jwks_public_keys: dict = {}
async def get_public_keys() -> dict:
"""获取 Keycloak 的公钥(用于 JWT 验证)"""
if _jwks_public_keys:
return _jwks_public_keys
async with httpx.AsyncClient() as client:
resp = await client.get(KEYCLOAK_JWKS_URL)
resp.raise_for_status()
jwks = resp.json()
for key in jwks.get("keys", []):
# 从 JWK 构造 PEM 公钥
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_public_key
public_numbers = rsa.RSAPublicNumbers(
e=int.from_bytes(bytes.fromhex(key.get("e", "")), "big"),
n=int.from_bytes(bytes.fromhex(key.get("n", "")), "big"),
)
pub_key = public_numbers.public_key(default_backend())
_jwks_public_keys[key.get("kid")] = pub_key
return _jwks_public_keys
# ==================== 策略决策 ====================
class PolicyDecision(BaseModel):
"""策略决策结果"""
allowed: bool
reason: str
risk_level: str = "low" # low / medium / high
def evaluate_risk(request: Request, token_payload: dict) -> str:
"""
评估访问风险等级
综合考虑:IP、User-Agent、时间、设备指纹等
"""
risk_score = 0
# 1. 检查访问时间(非工作时间风险较高)
hour = time.localtime().tm_hour
if hour < 6 or hour > 22:
risk_score += 20
# 2. 检查 IP 是否在白名单
client_ip = request.client.host if request.client else "unknown"
trusted_ips = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
if not any(client_ip.startswith(prefix.rstrip("/").rsplit(".", 1)[0]) for prefix in trusted_ips):
risk_score += 15
# 3. 检查 token 签发时间(刚签发的 token 更可信)
iat = token_payload.get("iat", 0)
token_age = time.time() - iat
if token_age > 3600: # 超过1小时
risk_score += 10
# 4. 检查用户角色
realm_access = token_payload.get("realm_access", {})
roles = realm_access.get("roles", [])
if "admin" in roles:
risk_score -= 10
if "viewer" in roles:
risk_score += 5
# 风险等级判定
if risk_score >= 30:
return "high"
elif risk_score >= 15:
return "medium"
return "low"
def check_permission(path: str, method: str, roles: list[str]) -> bool:
"""
细粒度权限检查
定义每个 API 端点的访问策略
"""
# 策略定义:(路径前缀, HTTP方法, 允许的角色列表)
policies = [
("/api/users", "GET", ["admin", "developer", "viewer"]),
("/api/users", "POST", ["admin"]),
("/api/users", "DELETE", ["admin"]),
("/api/orders", "GET", ["admin", "developer"]),
("/api/orders", "POST", ["admin", "developer"]),
("/api/analytics", "GET", ["admin", "developer"]),
("/api/analytics", "POST", ["admin"]),
]
for policy_path, policy_method, allowed_roles in policies:
if path.startswith(policy_path) and method.upper() == policy_method:
return bool(set(roles) & set(allowed_roles))
# 默认拒绝
return False
# ==================== 认证中间件 ====================
async def verify_token(request: Request) -> PolicyDecision:
"""验证 JWT Token 并做出策略决策"""
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return PolicyDecision(allowed=False, reason="缺少 Bearer Token", risk_level="high")
token = auth_header[7:]
try:
public_keys = await get_public_keys()
# 尝试每个公钥进行验证
payload = None
for kid, pub_key in public_keys.items():
try:
payload = jwt.decode(
token,
pub_key,
algorithms=["RS256"],
audience=AUDIENCE,
)
break
except jwt.InvalidKeyError:
continue
if payload is None:
return PolicyDecision(allowed=False, reason="Token 验证失败", risk_level="high")
# 检查 token 过期
exp = payload.get("exp", 0)
if time.time() > exp:
return PolicyDecision(allowed=False, reason="Token 已过期", risk_level="high")
# 获取用户角色
roles = payload.get("realm_access", {}).get("roles", [])
# 检查权限
path = request.url.path
method = request.method
if not check_permission(path, method, roles):
return PolicyDecision(
allowed=False,
reason=f"权限不足:{roles} 无权访问 {method} {path}",
risk_level="medium"
)
# 评估风险
risk_level = evaluate_risk(request, payload)
# 高风险需要额外验证(可以扩展为 MFA)
if risk_level == "high":
mfa_token = request.headers.get("X-MFA-Token")
if not mfa_token:
return PolicyDecision(
allowed=False,
reason="高风险访问,需要二次认证(MFA)",
risk_level="high"
)
# 将用户信息注入请求上下文
request.state.user_id = payload.get("sub")
request.state.roles = roles
request.state.risk_level = risk_level
return PolicyDecision(allowed=True, reason="验证通过", risk_level=risk_level)
except jwt.ExpiredSignatureError:
return PolicyDecision(allowed=False, reason="Token 已过期", risk_level="high")
except Exception as e:
return PolicyDecision(allowed=False, reason=f"验证异常:{str(e)}", risk_level="high")
# ==================== 请求代理 ====================
@app.middleware("http")
async def zero_trust_middleware(request: Request, call_next):
"""零信任中间件:拦截所有请求进行策略决策"""
# 健康检查等公开端点跳过验证
public_paths = {"/health", "/metrics", "/docs", "/openapi.json"}
if request.url.path in public_paths:
return await call_next(request)
# 策略决策
decision = await verify_token(request)
if not decision.allowed:
status_code = 401 if "Token" in decision.reason else 403
return JSONResponse(
status_code=status_code,
content={
"error": decision.reason,
"risk_level": decision.risk_level,
"policy": "zero-trust",
}
)
# 在响应头中附加风险信息
response = await call_next(request)
response.headers["X-Risk-Level"] = decision.risk_level
response.headers["X-Policy"] = "zero-trust"
return response
@app.get("/health")
async def health():
return {"status": "ok", "policy": "zero-trust"}
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(request: Request):
"""代理请求到后端服务"""
path = "/" + request.path_params["path"]
# 找到匹配的后端服务
backend_url = None
for prefix, url in BACKEND_SERVICES.items():
if path.startswith(prefix):
backend_url = url + path
break
if not backend_url:
raise HTTPException(status_code=404, detail="服务未找到")
# 转发请求
body = await request.body()
headers = dict(request.headers)
# 注入用户信息到后端
headers["X-User-Id"] = getattr(request.state, "user_id", "")
headers["X-User-Roles"] = ",".join(getattr(request.state, "roles", []))
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.request(
method=request.method,
url=backend_url,
content=body,
headers=headers,
)
return JSONResponse(
status_code=response.status_code,
content=response.json()
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
第三步:实现服务间零信任通信
微服务之间的调用也不能"信任内网"。每个服务都需要验证调用方的身份。
"""
服务间 mTLS 通信工具
每个服务既是客户端也是服务端,双向 TLS 认证
"""
import ssl
import httpx
from pathlib import Path
def create_mtls_context(cert_dir: str) -> ssl.SSLContext:
"""
创建 mTLS SSL 上下文
cert_dir: 存放证书的目录
"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.minimum_version = ssl.TLSVersion.TLSv1_3
# 加载 CA 证书(用于验证对端)
ctx.load_verify_locations(cafile=str(Path(cert_dir) / "ca.crt"))
# 加载本服务证书和私钥
ctx.load_cert_chain(
certfile=str(Path(cert_dir) / "server.crt"),
keyfile=str(Path(cert_dir) / "server.key"),
)
# 强制客户端证书验证
ctx.verify_mode = ssl.CERT_REQUIRED
return ctx
def create_mtls_client(cert_dir: str) -> httpx.AsyncClient:
"""
创建支持 mTLS 的 HTTP 客户端
用于服务间调用
"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.minimum_version = ssl.TLSVersion.TLSv1_3
# 加载 CA 证书
ctx.load_verify_locations(cafile=str(Path(cert_dir) / "ca.crt"))
# 加载客户端证书和私钥
ctx.load_cert_chain(
certfile=str(Path(cert_dir) / "client.crt"),
keyfile=str(Path(cert_dir) / "client.key"),
)
transport = httpx.AsyncHTTPTransport(verify=ctx)
return httpx.AsyncClient(transport=transport, timeout=30.0)
# ==================== 使用示例 ====================
async def call_order_service():
"""
用户服务调用订单服务的示例
使用 mTLS + JWT 双重验证
"""
import jwt
import time
# 1. 生成服务间 JWT
service_token = jwt.encode(
payload={
"sub": "user-service",
"aud": "order-service",
"iat": int(time.time()),
"exp": int(time.time()) + 300, # 5分钟有效期
"scope": "read:orders",
},
key="your-service-secret-key",
algorithm="HS256",
)
# 2. 通过 mTLS 发起请求
client = create_mtls_client("/etc/certs")
response = await client.get(
"https://order-service:8002/api/orders/123",
headers={
"Authorization": f"Bearer {service_token}",
"X-Service-Name": "user-service",
}
)
return response.json()
第四步:审计日志与异常检测
零信任架构要求对所有访问行为进行持续监控和审计。
"""
零信任审计日志系统
记录所有访问行为,支持异常检测
"""
import time
import json
import logging
from datetime import datetime
from fastapi import FastAPI, Request
from collections import defaultdict
logger = logging.getLogger("zero-trust-audit")
# 内存存储(生产环境用 Elasticsearch 或日志服务)
audit_log: list[dict] = []
# 用户访问频率统计
access_stats: dict[str, list[float]] = defaultdict(list)
class AuditLogger:
"""审计日志记录器"""
def __init__(self):
self.log = audit_log
def record(self, request: Request, decision: dict, user_id: str = None):
"""记录一条审计日志"""
entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"source_ip": request.client.host if request.client else "unknown",
"user_id": user_id,
"method": request.method,
"path": request.url.path,
"user_agent": request.headers.get("User-Agent", ""),
"risk_level": decision.get("risk_level", "unknown"),
"decision": "allowed" if decision.get("allowed") else "denied",
"reason": decision.get("reason", ""),
}
self.log.append(entry)
# 异常检测
self._check_anomaly(entry)
logger.info(json.dumps(entry, ensure_ascii=False))
def _check_anomaly(self, entry: dict):
"""
简单异常检测逻辑
生产环境建议用机器学习模型
"""
user = entry.get("user_id", "")
if not user:
return
now = time.time()
access_stats[user].append(now)
# 清理 10 分钟前的记录
access_stats[user] = [t for t in access_stats[user] if now - t < 600]
# 10 分钟内超过 100 次访问 -> 告警
if len(access_stats[user]) > 100:
logger.warning(
f"异常访问检测:用户 {user} 在 10 分钟内访问 {len(access_stats[user])} 次"
)
# 检查是否从不同 IP 快速切换
recent_ips = set()
for log_entry in self.log[-50:]:
if log_entry.get("user_id") == user:
recent_ips.add(log_entry.get("source_ip", ""))
if len(recent_ips) > 5:
logger.warning(
f"异常访问检测:用户 {user} 近期从 {len(recent_ips)} 个不同 IP 访问"
)
def get_user_activity(self, user_id: str, hours: int = 24) -> list[dict]:
"""查询指定用户的活动记录"""
cutoff = time.time() - hours * 3600
return [
entry for entry in self.log
if entry.get("user_id") == user_id
and datetime.fromisoformat(entry["timestamp"].replace("Z", "+00:00")).timestamp() > cutoff
]
# 全局审计实例
audit = AuditLogger()
第五步:云厂商零信任方案对比
如果你不想自己搭建,各大云厂商也提供了零信任解决方案:
| 方案 | 腾讯云 | 阿里云 | 华为云 |
|---|---|---|---|
| 身份管理 | CAM + 企业微信 | RAM + 钉钉 | IAM |
| 网络访问控制 | 云 API 网关 + 私有网络 | SAG + PrivateLink | 虚拟私有云 |
| 零信任网关 | Tencent EdgeOne | SASE 解决方案 | 企业零信任方案 |
| 数据安全 | 数据安全中心 | 数据安全中心 | 数据安全中心 |
| 堡垒机 | 云堡垒机 | 堡垒机 | 云堡垒机 |
| 免费额度 | API 网关 100 万次/月 | SAG 体验版 | 30 天试用 |
总结
零信任架构不是一朝一夕能完成的改造,而是一个渐进式的安全演进过程。建议按以下路径推进:
- 第一阶段:统一身份认证(Keycloak / 云厂商 IAM)
- 第二阶段:API 网关策略执行(PEP / PDP 分离)
- 第三阶段:服务间 mTLS 加密通信
- 第四阶段:全量审计日志 + 异常检测
- 第五阶段:自适应策略(基于风险的动态调整)
安全无小事,零信任是 2026 年云上安全的标配。如果你在云安全方案选型上有疑问,欢迎交流(主营腾讯云/阿里云/火山云)。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~
博客:yunduancloud.icu