12微服务架构设计模式

微服务的安全

安全架构模式

OAuth2JWT零信任

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第12章 微服务的安全 - 安全架构模式 (Microservices Security: Security Architecture Patterns)

一、导读

1.1 本章学习目标

通过本章的学习,读者将能够:

理解微服务架构中的安全挑战和与传统单体安全的本质区别

掌握身份认证和授权的基本概念及其在微服务中的实现方式

深入理解 OAuth 2.0 和 OpenID Connect 协议的工作原理和应用场景

学会使用 JWT(JSON Web Token)实现无状态的身份认证

掌握 API 网关在安全架构中的作用和配置方法

了解服务间通信的安全机制,包括 mTLS 和服务网格

学会实施数据加密、密钥管理和审计日志等安全措施

掌握常见的安全威胁和防护策略,构建纵深防御体系

1.2 为什么本章如此重要

安全是企业应用开发的基石。在单体应用中,安全相对集中:一个认证模块、一个会话管理、一套权限控制。但在微服务架构中,安全变得复杂得多。

微服务安全面临以下独特挑战:

  • 攻击面扩大:每个服务都是一个潜在的入口点
  • 服务间信任:如何确保服务间通信的安全性
  • 分布式会话:如何在多个服务间共享用户身份
  • 密钥管理:如何安全地存储和分发密钥
  • 合规要求:如何满足 GDPR、PCI DSS 等法规要求

如果安全措施不当,可能导致:

  • 数据泄露:用户敏感信息被窃取
  • 未授权访问:恶意用户绕过权限控制
  • 服务劫持:攻击者冒充合法服务
  • 中间人攻击:通信内容被窃听或篡改
  • 合规风险:违反法规导致罚款和声誉损失

正确的安全架构可以带来:

  • 统一认证:用户一次登录,访问所有服务
  • 细粒度授权:基于角色和资源的精确权限控制
  • 端到端加密:保护数据在传输和存储中的安全
  • 审计追溯:记录所有操作,便于问题排查
  • 合规保障:满足行业法规和标准要求

1.3 核心问题引导

微服务安全与单体安全有哪些本质区别?带来了哪些新挑战?

OAuth 2.0 和 OpenID Connect 的核心概念是什么?它们如何解决认证和授权问题?

JWT 的工作原理是什么?它的优势和局限性有哪些?

API 网关在安全架构中扮演什么角色?如何实现统一的认证和授权?

如何保护服务间通信的安全?mTLS 和服务网格如何帮助实现这一目标?

如何安全地存储和管理密钥、密码等敏感信息?

常见的安全威胁有哪些?如何构建纵深防御体系?

如何实现审计日志和合规要求?


二、核心概念详解

12.1 微服务安全的核心挑战

挑战一:攻击面扩大

在单体应用中,通常只有一个入口点(如 Web 应用或 API 端点)。安全防护可以集中在这一入口:

单体应用:
  客户端 → [防火墙] → [认证模块] → [业务逻辑] → [数据库]

但在微服务中,每个服务都可能暴露 API:

微服务架构:
  客户端 → 服务 A (公开 API)
         → 服务 B (公开 API)
         → 服务 C (内部 API,但可能被探测)
         → 服务 D (内部 API,但可能被探测)
         → ...

每个暴露的 API 端点都是潜在的攻击入口。

挑战二:服务间信任

在单体中,模块间调用是进程内的,天然可信。但在微服务中:

  • 服务 A 调用服务 B,如何确保 A 是合法的?
  • 服务 B 收到请求,如何验证请求未被篡改?
  • 恶意服务如何防止?

挑战三:分布式会话管理

单体应用可以使用服务器端会话(Session)存储用户状态。但在微服务中:

  • 用户请求可能路由到不同的服务实例
  • 会话数据需要在多个服务间共享
  • 水平扩展时会话粘性变得复杂

挑战四:密钥管理

微服务系统需要管理多种密钥:

  • API 密钥
  • JWT 签名密钥
  • 数据库密码
  • TLS 证书
  • 第三方服务凭证

密钥的分发、轮换和撤销变得复杂。

挑战五:合规要求

不同行业和地区有不同的合规要求:

  • GDPR(欧盟):个人数据保护
  • PCI DSS(支付行业):信用卡数据安全
  • HIPAA(医疗行业):患者隐私保护
  • 等保 2.0(中国):网络安全等级保护

12.2 身份认证与授权基础

身份认证(Authentication):验证用户是谁。

常见方式:

  • 用户名/密码
  • 短信验证码
  • 生物识别(指纹、面部)
  • 多因素认证(MFA)

授权(Authorization):验证用户可以做什么。

常见模型:

  • RBAC(基于角色的访问控制):用户属于角色,角色拥有权限
  • ABAC(基于属性的访问控制):根据用户属性、资源属性、环境属性决定权限
  • ACL(访问控制列表):直接为用户或组分配权限

在微服务中的实现:

客户端请求 → API 网关
              ↓
         [认证:验证用户身份]
              ↓
         [授权:检查用户权限]
              ↓
         路由到后端服务
              ↓
         [服务内二次授权:细粒度权限]

12.3 OAuth 2.0 与 OpenID Connect

OAuth 2.0 核心概念:

OAuth 2.0 是一个授权框架,允许用户授权第三方应用访问其资源,而无需共享密码。

核心角色:

Resource Owner(资源所有者):用户

Client(客户端):请求访问资源的应用

Resource Server(资源服务器):存储受保护资源的服务

Authorization Server(授权服务器):颁发令牌的服务

授权流程(Authorization Code Grant):

1. 客户端重定向用户到授权服务器
2. 用户登录并授权
3. 授权服务器返回授权码
4. 客户端用授权码换取访问令牌(Access Token)
5. 客户端用访问令牌访问资源服务器
6. 资源服务器验证令牌并返回资源

OpenID Connect(OIDC):

OIDC 是基于 OAuth 2.0 的身份认证层。它在 OAuth 2.0 的基础上增加了:

  • ID Token:包含用户身份信息的 JWT
  • UserInfo Endpoint:获取用户详细信息的 API
  • Discovery:自动发现授权服务器的配置

OIDC 流程:

1. 客户端重定向用户到 OIDC Provider
2. 用户登录
3. Provider 返回授权码
4. 客户端用授权码换取 Access Token 和 ID Token
5. 客户端解析 ID Token 获取用户身份
6. 客户端用 Access Token 访问资源

Spring Security OAuth2 示例:

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(endpoint -> endpoint
                    .baseUri("/oauth2/authorization")
                )
                .redirectionEndpoint(redirection -> redirection
                    .baseUri("/login/oauth2/code")
                )
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                )
            );
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withIssuerLocation(
            "https://auth.example.com/.well-known/openid-configuration"
        ).build();
    }
}

12.4 JWT(JSON Web Token)

JWT 结构:

JWT 由三部分组成,用点号分隔:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header(头部):指定算法和类型

json
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload(载荷):包含声明(Claims)

json
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "roles": ["USER", "ADMIN"]
}

Signature(签名):验证令牌完整性

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

JWT 的优势:

  • 无状态:服务端不需要存储会话
  • 可扩展:可以在载荷中添加自定义声明
  • 跨域友好:适合微服务架构
  • 自包含:令牌包含所有必要信息

JWT 的局限性:

  • 无法撤销:一旦签发,在过期前一直有效
  • 大小限制:载荷不宜过大
  • 安全性依赖密钥:密钥泄露会导致严重问题

JWT 生成和验证:

java
@Service
public class JwtTokenService {
    
    private final String secretKey;
    private final long validityInMs;
    
    public JwtTokenService(@Value("${jwt.secret}") String secretKey,
                           @Value("${jwt.validity}") long validityInMs) {
        this.secretKey = secretKey;
        this.validityInMs = validityInMs;
    }
    
    public String createToken(UserDetails userDetails) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMs);
        
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .claim("roles", userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()))
            .setIssuedAt(now)
            .setExpiration(validity)
            .signWith(SignatureAlgorithm.HS256, secretKey)
            .compact();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
    
    public String getUsername(String token) {
        return Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }
    
    public List<String> getRoles(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(token)
            .getBody();
        
        return claims.get("roles", List.class);
    }
}

JWT 过滤器:

java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final JwtTokenService tokenService;
    private final UserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        
        String token = resolveToken(request);
        
        if (token != null && tokenService.validateToken(token)) {
            String username = tokenService.getUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
                );
            authentication.setDetails(
                new WebAuthenticationDetailsSource().buildDetails(request)
            );
            
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

12.5 API 网关与安全

API 网关的安全职责:

统一认证:在网关层验证用户身份

授权检查:基于路由和角色进行初步授权

速率限制:防止滥用和 DDoS 攻击

IP 白名单:限制访问来源

请求验证:过滤恶意请求

SSL 终止:处理 HTTPS 加密

Spring Cloud Gateway 配置:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - AuthenticationFilter
            - AuthorizationFilter
            - RateLimitFilter
        
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - AuthenticationFilter
            - AuthorizationFilter

# 速率限制配置
filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20

自定义认证过滤器:

java
@Component
public class AuthenticationFilter implements GlobalFilter {
    
    private final JwtTokenService tokenService;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = extractToken(exchange.getRequest());
        
        if (token == null || !tokenService.validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        // 将用户信息传递给下游服务
        String username = tokenService.getUsername(token);
        List<String> roles = tokenService.getRoles(token);
        
        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
            .header("X-User-Name", username)
            .header("X-User-Roles", String.join(",", roles))
            .build();
        
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }
    
    private String extractToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
}

自定义授权过滤器:

java
@Component
public class AuthorizationFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();
        List<String> roles = extractRoles(request);
        
        // 基于路径的授权规则
        if (path.startsWith("/api/admin") && !roles.contains("ADMIN")) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }
        
        if (path.startsWith("/api/orders") && 
            !roles.contains("ADMIN") && !roles.contains("USER")) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }
        
        return chain.filter(exchange);
    }
    
    private List<String> extractRoles(ServerHttpRequest request) {
        String rolesHeader = request.getHeaders().getFirst("X-User-Roles");
        if (rolesHeader != null) {
            return Arrays.asList(rolesHeader.split(","));
        }
        return Collections.emptyList();
    }
}

12.6 服务间通信安全

mTLS(双向 TLS)

mTLS 要求客户端和服务器都提供证书,实现双向认证:

客户端                          服务器
  |                               |
  |--- Client Hello ------------->|
  |<-- Server Hello + Cert -------|
  |--- Client Cert -------------->|
  |--- Key Exchange ------------->|
  |<-- Server Ack ----------------|
  |=== 加密通信 ==================|

优势:

  • 双方身份都被验证
  • 通信内容加密
  • 防止中间人攻击

Istio 实现 mTLS:

yaml
# 启用严格 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT
yaml
# 允许特定服务不使用 mTLS(如遗留系统)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: legacy-service
  namespace: production
spec:
  selector:
    matchLabels:
      app: legacy-service
  mtls:
    mode: PERMISSIVE

服务间令牌传递

在服务间调用时传递用户身份:

java
@Service
@RequiredArgsConstructor
public class OrderService {
    
    private final ProductServiceClient productService;
    
    public Order createOrder(OrderRequest request) {
        // 获取当前用户身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();
        
        // 创建传播令牌
        String propagationToken = createPropagationToken(username);
        
        // 调用其他服务时携带令牌
        productService.checkAvailability(
            request.getItems(),
            propagationToken
        );
        
        // ...
    }
    
    private String createPropagationToken(String username) {
        // 创建短期的内部令牌
        return Jwts.builder()
            .setSubject(username)
            .setIssuer("order-service")
            .setAudience("product-service")
            .setExpiration(new Date(System.currentTimeMillis() + 60000)) // 1分钟
            .signWith(internalKey)
            .compact();
    }
}

Feign 拦截器传递令牌:

java
@Component
public class FeignTokenInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate template) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication != null && authentication.getCredentials() instanceof String) {
            String token = (String) authentication.getCredentials();
            template.header("Authorization", "Bearer " + token);
        }
    }
}

12.7 数据加密与密钥管理

数据传输加密

  • HTTPS/TLS:所有外部通信必须使用 HTTPS
  • 内部通信:使用 mTLS 或 VPN

数据存储加密

  • 数据库加密:使用 TDE(透明数据加密)
  • 字段级加密:敏感字段(如身份证号、银行卡号)单独加密
  • 密钥分离:加密密钥与数据分开存储

Spring Data 字段加密示例:

java
@Entity
public class Customer {
    
    @Id
    private String id;
    
    private String name;
    
    @Column(name = "encrypted_email")
    private String encryptedEmail;
    
    @Transient
    private String email;
    
    @PrePersist
    @PreUpdate
    public void encryptSensitiveData() {
        if (email != null) {
            this.encryptedEmail = encryptionService.encrypt(email);
        }
    }
    
    @PostLoad
    public void decryptSensitiveData() {
        if (encryptedEmail != null) {
            this.email = encryptionService.decrypt(encryptedEmail);
        }
    }
}

@Service
public class EncryptionService {
    
    @Value("${encryption.key}")
    private String encryptionKey;
    
    public String encrypt(String plaintext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(
                Base64.getDecoder().decode(encryptionKey), "AES"
            );
            GCMParameterSpec gcmSpec = new GCMParameterSpec(128, new byte[12]);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            
            byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(ciphertext);
        } catch (Exception e) {
            throw new EncryptionException("加密失败", e);
        }
    }
    
    public String decrypt(String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(
                Base64.getDecoder().decode(encryptionKey), "AES"
            );
            GCMParameterSpec gcmSpec = new GCMParameterSpec(128, new byte[12]);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
            
            byte[] plaintext = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
            return new String(plaintext, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new EncryptionException("解密失败", e);
        }
    }
}

HashiCorp Vault 密钥管理:

java
@Service
public class VaultSecretService {
    
    private final VaultTemplate vaultTemplate;
    
    public String getDatabasePassword() {
        return vaultTemplate.read("secret/database", Map.class)
            .getData()
            .get("password")
            .toString();
    }
    
    public String getJwtSigningKey() {
        return vaultTemplate.read("secret/jwt", Map.class)
            .getData()
            .get("signing-key")
            .toString();
    }
    
    public void rotateDatabasePassword() {
        // 生成新密码
        String newPassword = generateSecurePassword();
        
        // 更新数据库
        updateDatabasePassword(newPassword);
        
        // 更新 Vault
        vaultTemplate.write("secret/database", Map.of("password", newPassword));
    }
}

Kubernetes Secrets:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secrets
type: Opaque
data:
  db-password: cGFzc3dvcmQxMjM=  # base64 encoded
  jwt-secret: c2VjcmV0a2V5MTIz
java
@Configuration
public class SecretConfig {
    
    @Value("${DB_PASSWORD}")
    private String dbPassword;
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://db-host:5432/orders");
        config.setUsername("order_user");
        config.setPassword(dbPassword);  // 从环境变量读取
        return new HikariDataSource(config);
    }
}

12.8 审计日志与合规

审计日志要求:

  • 谁:操作用户
  • 何时:操作时间
  • 何地:操作来源(IP 地址)
  • 做了什么:操作类型和资源
  • 结果:成功或失败

Spring Boot 审计日志:

java
@Component
public class AuditLogger {
    
    private static final Logger logger = LoggerFactory.getLogger("AUDIT");
    
    @EventListener
    public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
        Authentication authentication = event.getAuthentication();
        logger.info("AUTH_SUCCESS: user={}, ip={}, time={}",
            authentication.getName(),
            getClientIp(),
            LocalDateTime.now()
        );
    }
    
    @EventListener
    public void handleAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
        logger.warn("AUTH_FAILURE: user={}, ip={}, reason={}, time={}",
            event.getAuthentication().getName(),
            getClientIp(),
            event.getException().getMessage(),
            LocalDateTime.now()
        );
    }
    
    public void logDataAccess(String username, String resource, String action) {
        logger.info("DATA_ACCESS: user={}, resource={}, action={}, time={}",
            username, resource, action, LocalDateTime.now()
        );
    }
    
    private String getClientIp() {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            return attributes.getRequest().getRemoteAddr();
        }
        return "unknown";
    }
}

AOP 审计日志:

java
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {
    
    private final AuditLogger auditLogger;
    
    @Around("@annotation(Auditable)")
    public Object auditOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        Auditable auditable = getAnnotation(joinPoint);
        String username = getCurrentUser();
        String resource = auditable.resource();
        String action = auditable.action();
        
        try {
            Object result = joinPoint.proceed();
            auditLogger.logDataAccess(username, resource, action + " SUCCESS");
            return result;
        } catch (Exception e) {
            auditLogger.logDataAccess(username, resource, action + " FAILED: " + e.getMessage());
            throw e;
        }
    }
    
    private String getCurrentUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication != null ? authentication.getName() : "anonymous";
    }
    
    private Auditable getAnnotation(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod().getAnnotation(Auditable.class);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    String resource();
    String action();
}

// 使用
@Service
public class OrderService {
    
    @Auditable(resource = "order", action = "CREATE")
    public Order createOrder(OrderRequest request) {
        // ...
    }
    
    @Auditable(resource = "order", action = "DELETE")
    public void cancelOrder(String orderId) {
        // ...
    }
}

GDPR 合规要点:

数据最小化:只收集必要的个人数据

目的限制:明确数据使用目的

存储限制:设定数据保留期限

用户权利:支持数据访问、更正、删除

数据保护:加密、匿名化、访问控制

数据删除实现:

java
@Service
@RequiredArgsConstructor
public class UserDataDeletionService {
    
    private final CustomerRepository customerRepository;
    private final OrderRepository orderRepository;
    private final AuditLogger auditLogger;
    
    @Transactional
    public void deleteUserData(String customerId) {
        // 1. 匿名化订单数据(保留业务数据)
        orderRepository.findByCustomerId(customerId).forEach(order -> {
            order.setCustomerId(null);
            order.setCustomerEmail(null);
            orderRepository.save(order);
        });
        
        // 2. 删除客户个人信息
        customerRepository.deleteById(customerId);
        
        // 3. 记录审计日志
        auditLogger.logDataAccess("system", "customer:" + customerId, "GDPR_DELETION");
    }
}

12.9 常见安全威胁与防护

OWASP Top 10 微服务相关威胁:

注入攻击(Injection)

- SQL 注入

- NoSQL 注入

- 命令注入

防护:使用参数化查询、输入验证

失效的身份认证(Broken Authentication)

- 弱密码

- 会话固定

- 令牌泄露

防护:强密码策略、MFA、安全的令牌管理

敏感数据暴露(Sensitive Data Exposure)

- 未加密的数据

- 错误的 HTTPS 配置

防护:端到端加密、正确的 TLS 配置

XML 外部实体(XXE)

- XML 解析器漏洞

防护:禁用外部实体解析

失效的访问控制(Broken Access Control)

- 越权访问

- IDOR(不安全的直接对象引用)

防护:严格的授权检查、资源所有权验证

安全配置错误(Security Misconfiguration)

- 默认密码

- 开放的调试接口

防护:安全基线配置、定期审计

跨站脚本(XSS)

- 反射型 XSS

- 存储型 XSS

防护:输出编码、CSP 头

不安全的反序列化(Insecure Deserialization)

- 远程代码执行

防护:验证输入、使用安全的序列化格式

使用已知漏洞的组件(Using Components with Known Vulnerabilities)

- 过时的依赖库

防护:依赖扫描、及时更新

不足的日志记录和监控(Insufficient Logging & Monitoring)

- 攻击未被发现

防护:全面的审计日志、实时告警

速率限制实现:

java
@Component
public class RateLimitFilter implements GlobalFilter {
    
    private final RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String clientId = getClientId(exchange.getRequest());
        String key = "rate_limit:" + clientId;
        
        Long count = redisTemplate.opsForValue().increment(key);
        
        if (count == 1) {
            redisTemplate.expire(key, 60, TimeUnit.SECONDS);
        }
        
        if (count > 100) {  // 每分钟最多 100 次请求
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return exchange.getResponse().setComplete();
        }
        
        return chain.filter(exchange);
    }
    
    private String getClientId(ServerHttpRequest request) {
        String forwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
        return forwardedFor != null ? forwardedFor : request.getRemoteAddress().getAddress().getHostAddress();
    }
}

CORS 配置:

java
@Configuration
public class CorsConfig {
    
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        
        config.setAllowedOrigins(List.of("https://example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);
        
        source.registerCorsConfiguration("/api/**", config);
        
        return new CorsFilter(source);
    }
}

三、程序员的视角

3.1 在实际项目中的应用

安全开发流程:

威胁建模:在设计阶段识别潜在威胁

安全编码:遵循安全编码规范

代码审查:重点关注安全问题

安全测试:渗透测试、漏洞扫描

持续监控:生产环境的安全监控

调试技巧:

  • 使用 Postman 测试认证和授权
  • 查看 JWT 内容(jwt.io)
  • 检查 TLS 证书(openssl s_client)

常用工具:

  • OWASP ZAP:开源安全扫描工具
  • Burp Suite:专业的 Web 安全测试工具
  • Nmap:网络扫描工具
  • SonarQube:代码质量和安全扫描

3.2 常见陷阱与误区

陷阱一:信任客户端数据

永远不要信任来自客户端的数据,必须进行服务端验证。

陷阱二:硬编码密钥

密钥应该通过环境变量或密钥管理服务提供。

陷阱三:忽略日志中的敏感信息

日志中不应包含密码、令牌、个人数据等敏感信息。

陷阱四:过度依赖单一安全措施

应采用纵深防御策略,多层防护。

陷阱五:忽视内部威胁

内部人员也可能造成安全风险,需要适当的访问控制和审计。

3.3 与其他层级的联系

安全架构影响:

  • 架构设计:安全应该是架构的核心考虑
  • 运维:证书管理、密钥轮换、漏洞修补
  • 合规:满足法规和行业标准
  • 用户体验:平衡安全性和易用性

四、代码实战

4.1 完整的认证授权实现

java
// 安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http,
                                           JwtAuthenticationFilter jwtFilter) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**", "/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

// 认证控制器
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
    
    private final AuthenticationService authService;
    
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
        AuthResponse response = authService.authenticate(request);
        return ResponseEntity.ok(response);
    }
    
    @PostMapping("/register")
    public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest request) {
        AuthResponse response = authService.register(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshRequest request) {
        AuthResponse response = authService.refreshToken(request);
        return ResponseEntity.ok(response);
    }
}

// 认证服务
@Service
@RequiredArgsConstructor
@Transactional
public class AuthenticationService {
    
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenService tokenService;
    
    public AuthResponse authenticate(LoginRequest request) {
        User user = userRepository.findByEmail(request.getEmail())
            .orElseThrow(() -> new AuthenticationException("用户不存在"));
        
        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new AuthenticationException("密码错误");
        }
        
        if (!user.isEnabled()) {
            throw new AuthenticationException("账户已禁用");
        }
        
        String accessToken = tokenService.createAccessToken(user);
        String refreshToken = tokenService.createRefreshToken(user);
        
        return new AuthResponse(accessToken, refreshToken, user.toDto());
    }
    
    public AuthResponse register(RegisterRequest request) {
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new BusinessException("邮箱已被注册");
        }
        
        User user = new User();
        user.setEmail(request.getEmail());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setName(request.getName());
        user.setRole(Role.USER);
        user.setEnabled(true);
        
        userRepository.save(user);
        
        String accessToken = tokenService.createAccessToken(user);
        String refreshToken = tokenService.createRefreshToken(user);
        
        return new AuthResponse(accessToken, refreshToken, user.toDto());
    }
    
    public AuthResponse refreshToken(RefreshRequest request) {
        if (!tokenService.validateRefreshToken(request.getRefreshToken())) {
            throw new AuthenticationException("无效的刷新令牌");
        }
        
        String username = tokenService.getUsernameFromRefreshToken(request.getRefreshToken());
        User user = userRepository.findByEmail(username)
            .orElseThrow(() -> new AuthenticationException("用户不存在"));
        
        String newAccessToken = tokenService.createAccessToken(user);
        String newRefreshToken = tokenService.createRefreshToken(user);
        
        return new AuthResponse(newAccessToken, newRefreshToken, user.toDto());
    }
}

// 方法级授权
@Service
public class OrderService {
    
    @PreAuthorize("hasRole('ADMIN') or #orderId == authentication.principal.id")
    public Order getOrder(String orderId) {
        // 只有管理员或订单所有者可以查看
    }
    
    @PreAuthorize("hasRole('USER')")
    public Order createOrder(OrderRequest request) {
        // 所有登录用户可以创建订单
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    public void cancelOrder(String orderId) {
        // 只有管理员可以取消任意订单
    }
}

4.2 API 网关安全配置

yaml
# application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - name: AuthenticationFilter
            - name: AuthorizationFilter
              args:
                requiredRoles: USER,ADMIN
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
        
        - id: admin-service
          uri: lb://admin-service
          predicates:
            - Path=/api/admin/**
          filters:
            - name: AuthenticationFilter
            - name: AuthorizationFilter
              args:
                requiredRoles: ADMIN

server:
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: ${KEYSTORE_PASSWORD}
    key-store-type: PKCS12

4.3 安全测试

java
@SpringBootTest
@AutoConfigureMockMvc
class SecurityTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void shouldRejectUnauthenticatedRequest() throws Exception {
        mockMvc.perform(get("/api/orders"))
            .andExpect(status().isUnauthorized());
    }
    
    @Test
    void shouldAllowAuthenticatedRequest() throws Exception {
        String token = generateToken("user@example.com", List.of("USER"));
        
        mockMvc.perform(get("/api/orders")
                .header("Authorization", "Bearer " + token))
            .andExpect(status().isOk());
    }
    
    @Test
    void shouldRejectNonAdminAccess() throws Exception {
        String token = generateToken("user@example.com", List.of("USER"));
        
        mockMvc.perform(get("/api/admin/users")
                .header("Authorization", "Bearer " + token))
            .andExpect(status().isForbidden());
    }
    
    @Test
    void shouldAllowAdminAccess() throws Exception {
        String token = generateToken("admin@example.com", List.of("ADMIN"));
        
        mockMvc.perform(get("/api/admin/users")
                .header("Authorization", "Bearer " + token))
            .andExpect(status().isOk());
    }
    
    private String generateToken(String email, List<String> roles) {
        return Jwts.builder()
            .setSubject(email)
            .claim("roles", roles)
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .signWith(SignatureAlgorithm.HS256, "secret")
            .compact();
    }
}

五、深入思考

5.1 历史背景

安全架构经历了多个发展阶段:

  • perimeter 安全时代:防火墙保护内部网络,内部假设可信
  • 零信任时代:不信任任何网络,每次访问都需要验证
  • 身份为中心时代:身份成为新的安全边界
  • DevSecOps 时代:安全融入开发和运维全流程

5.2 前沿方向

零信任架构(Zero Trust):永不信任,始终验证

服务网格安全:通过 Sidecar 统一管理安全策略

机密计算(Confidential Computing):硬件级别的内存加密

AI 驱动的安全:使用机器学习检测异常行为

区块链身份:去中心化的身份管理

5.3 延伸阅读

  • Antonio Leiva. "Spring Security in Action". Manning, 2019
  • OWASP Top 10: owasp.org/www-project-top-ten
  • OAuth 2.0 规范:tools.ietf.org/html/rfc6749
  • OpenID Connect: openid.net/connect

六、实验笔记

实验一:实现完整的 OAuth 2.0 + OIDC 认证系统

目标:搭建一个基于 OAuth 2.0 和 OpenID Connect 的认证系统。

步骤:

使用 Keycloak 或 Auth0 作为身份提供商

配置 Spring Security OAuth2 客户端

实现 JWT 令牌验证

实现基于角色的授权

测试认证和授权流程

预期结果:用户可以通过 OAuth 2.0 登录,访问受保护的资源。

实验二:实施 mTLS 和服务网格安全

目标:使用 Istio 实现服务间的 mTLS 通信。

步骤:

在 Kubernetes 集群中安装 Istio

启用全局 mTLS

配置 PeerAuthentication 和 AuthorizationPolicy

测试服务间通信

验证未授权的访问被拒绝

预期结果:服务间通信加密,未授权的访问被阻止。


七、本章小结

本章系统介绍了微服务的安全架构模式。我们首先分析了微服务安全面临的独特挑战:攻击面扩大、服务间信任、分布式会话管理、密钥管理和合规要求。

身份认证和授权是安全的基础。OAuth 2.0 和 OpenID Connect 提供了标准化的认证和授权框架,JWT 实现了无状态的身份验证。API 网关作为统一入口,承担了认证、授权、速率限制等安全职责。

服务间通信安全同样重要。mTLS 提供了双向认证和加密通信,服务网格(如 Istio)简化了安全策略的管理和实施。服务间令牌传递确保了用户身份在整个调用链中的传播。

数据加密和密钥管理保护了敏感数据的安全。传输层使用 TLS 加密,存储层使用字段级加密。HashiCorp Vault 等工具提供了安全的密钥管理方案。

审计日志和合规是安全的重要组成部分。全面的审计日志有助于问题排查和合规审计。GDPR 等法规对个人数据保护提出了严格要求。

最后,我们介绍了常见的安全威胁和防护策略。OWASP Top 10 提供了安全风险的参考清单。纵深防御策略通过多层防护降低安全风险。

安全是一个持续的过程,需要从设计、开发、测试到运维的全生命周期关注。只有通过综合的安全措施,才能构建真正安全的微服务系统。


八、习题

概念对比:比较 OAuth 2.0 和 OpenID Connect 的区别,说明各自的应用场景。

JWT 实现:实现一个 JWT 令牌生成和验证服务,支持访问令牌和刷新令牌。

API 网关安全:配置 Spring Cloud Gateway 实现统一认证、授权和速率限制。

mTLS 配置:使用 Istio 配置服务间的 mTLS 通信,包括严格模式和宽容模式。

数据加密:实现数据库字段的加密和解密,确保敏感数据的安全存储。

审计日志:使用 AOP 实现方法级的审计日志,记录关键操作。

安全测试:编写安全测试用例,验证认证、授权和输入验证的有效性。

GDPR 合规:设计一个数据删除功能,满足 GDPR 的被遗忘权要求。

威胁建模:为一个电商系统进行威胁建模,识别潜在的安全风险并提出防护措施。

案例分析:研究某大型互联网公司的安全架构,总结其设计原则和最佳实践。

12/12微服务的
0%