9.1 微服务安全概述

1. 微服务安全挑战

微服务架构相比单体应用在安全方面面临更多挑战:

安全边界扩大

  1. 网络边界增多

    • 服务间通信增加攻击面
    • 内部网络不再安全
    • 需要端到端加密
  2. 身份认证复杂

    • 多服务身份管理
    • 服务间信任关系
    • 用户身份传播
  3. 授权控制分散

    • 细粒度权限控制
    • 跨服务授权
    • 动态权限管理

主要安全威胁

  1. 网络攻击

    • 中间人攻击
    • 网络窃听
    • DDoS攻击
  2. 身份伪造

    • 服务身份伪造
    • 用户身份盗用
    • 令牌劫持
  3. 权限滥用

    • 权限提升
    • 越权访问
    • 数据泄露

2. 安全架构设计原则

零信任架构

  1. 永不信任,始终验证

    • 不信任网络位置
    • 验证每个请求
    • 最小权限原则
  2. 深度防御

    • 多层安全控制
    • 冗余安全机制
    • 故障安全设计
  3. 持续监控

    • 实时安全监控
    • 异常行为检测
    • 安全事件响应

安全设计模式

  1. 网关安全模式

    • 统一入口控制
    • 集中认证授权
    • 流量监控过滤
  2. 令牌传播模式

    • JWT令牌传递
    • 上下文传播
    • 令牌刷新机制
  3. 服务网格安全

    • mTLS通信加密
    • 服务身份管理
    • 策略统一管理

9.2 Spring Security 集成

1. Spring Security 基础配置

依赖配置

pom.xml

<dependencies>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <!-- OAuth2 Resource Server -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    
    <!-- JWT Support -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>
    
    <!-- Spring Cloud Security -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
</dependencies>

安全配置类

SecurityConfiguration.java

package com.example.orderservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Spring Security 配置
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration {
    
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtRequestFilter jwtRequestFilter;
    
    public SecurityConfiguration(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
                               JwtRequestFilter jwtRequestFilter) {
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtRequestFilter = jwtRequestFilter;
    }
    
    /**
     * 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    /**
     * 用户详情服务(示例用内存存储)
     */
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin123"))
            .roles("ADMIN")
            .build();
            
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("user123"))
            .roles("USER")
            .build();
            
        return new InMemoryUserDetailsManager(admin, user);
    }
    
    /**
     * 安全过滤器链配置
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF(API服务通常不需要)
            .csrf().disable()
            
            // 配置授权规则
            .authorizeHttpRequests(authz -> authz
                // 公开端点
                .requestMatchers("/auth/**", "/actuator/health", "/swagger-ui/**", "/v3/api-docs/**")
                .permitAll()
                
                // 管理员端点
                .requestMatchers("/admin/**")
                .hasRole("ADMIN")
                
                // 用户端点
                .requestMatchers("/orders/**")
                .hasAnyRole("USER", "ADMIN")
                
                // 其他请求需要认证
                .anyRequest()
                .authenticated()
            )
            
            // 异常处理
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
            )
            
            // 无状态会话
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            
            // 添加JWT过滤器
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
            
        return http.build();
    }
}

2. JWT 认证实现

JWT 工具类

JwtTokenUtil.java

package com.example.orderservice.security;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * JWT 令牌工具类
 */
@Component
@Slf4j
public class JwtTokenUtil {
    
    @Value("${jwt.secret:mySecretKey}")
    private String secret;
    
    @Value("${jwt.expiration:86400}")
    private Long expiration;
    
    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }
    
    /**
     * 从令牌中获取用户名
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    
    /**
     * 从令牌中获取过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    
    /**
     * 从令牌中获取指定声明
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    
    /**
     * 从令牌中获取所有声明
     */
    private Claims getAllClaimsFromToken(String token) {
        try {
            return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        } catch (ExpiredJwtException e) {
            log.warn("JWT token is expired: {}", e.getMessage());
            throw e;
        } catch (UnsupportedJwtException e) {
            log.warn("JWT token is unsupported: {}", e.getMessage());
            throw e;
        } catch (MalformedJwtException e) {
            log.warn("JWT token is malformed: {}", e.getMessage());
            throw e;
        } catch (SecurityException e) {
            log.warn("JWT signature validation failed: {}", e.getMessage());
            throw e;
        } catch (IllegalArgumentException e) {
            log.warn("JWT token compact of handler are invalid: {}", e.getMessage());
            throw e;
        }
    }
    
    /**
     * 检查令牌是否过期
     */
    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    
    /**
     * 为用户生成令牌
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities());
        return createToken(claims, userDetails.getUsername());
    }
    
    /**
     * 创建令牌
     */
    private String createToken(Map<String, Object> claims, String subject) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration * 1000);
        
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(subject)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(getSigningKey(), SignatureAlgorithm.HS512)
            .compact();
    }
    
    /**
     * 验证令牌
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        try {
            final String username = getUsernameFromToken(token);
            return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
        } catch (Exception e) {
            log.error("JWT token validation failed", e);
            return false;
        }
    }
    
    /**
     * 刷新令牌
     */
    public String refreshToken(String token) {
        try {
            final Claims claims = getAllClaimsFromToken(token);
            claims.setIssuedAt(new Date());
            claims.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000));
            
            return Jwts.builder()
                .setClaims(claims)
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
        } catch (Exception e) {
            log.error("Failed to refresh JWT token", e);
            throw new RuntimeException("Token refresh failed", e);
        }
    }
    
    /**
     * 从令牌中获取用户角色
     */
    public String[] getRolesFromToken(String token) {
        try {
            Claims claims = getAllClaimsFromToken(token);
            @SuppressWarnings("unchecked")
            List<Map<String, String>> roles = (List<Map<String, String>>) claims.get("roles");
            
            return roles.stream()
                .map(role -> role.get("authority"))
                .toArray(String[]::new);
        } catch (Exception e) {
            log.error("Failed to extract roles from token", e);
            return new String[0];
        }
    }
}

JWT 请求过滤器

JwtRequestFilter.java

package com.example.orderservice.security;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * JWT 请求过滤器
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter {
    
    private final UserDetailsService userDetailsService;
    private final JwtTokenUtil jwtTokenUtil;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain) throws ServletException, IOException {
        
        final String requestTokenHeader = request.getHeader("Authorization");
        
        String username = null;
        String jwtToken = null;
        
        // JWT Token 格式: "Bearer token"
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
            } catch (Exception e) {
                log.warn("Unable to get JWT Token or JWT Token has expired: {}", e.getMessage());
            }
        } else {
            log.debug("JWT Token does not begin with Bearer String");
        }
        
        // 验证令牌
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            
            // 如果令牌有效,配置Spring Security手动设置认证
            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                
                UsernamePasswordAuthenticationToken authToken = 
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                        
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                // 设置认证信息到安全上下文
                SecurityContextHolder.getContext().setAuthentication(authToken);
                
                log.debug("JWT authentication successful for user: {}", username);
            } else {
                log.warn("JWT token validation failed for user: {}", username);
            }
        }
        
        chain.doFilter(request, response);
    }
}

认证入口点

JwtAuthenticationEntryPoint.java

package com.example.orderservice.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT 认证入口点
 */
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public void commence(HttpServletRequest request, 
                        HttpServletResponse response,
                        AuthenticationException authException) throws IOException {
        
        log.warn("Unauthorized access attempt: {}", authException.getMessage());
        
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        
        Map<String, Object> body = new HashMap<>();
        body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
        body.put("error", "Unauthorized");
        body.put("message", "Access denied: " + authException.getMessage());
        body.put("path", request.getServletPath());
        body.put("timestamp", System.currentTimeMillis());
        
        objectMapper.writeValue(response.getOutputStream(), body);
    }
}

3. 认证控制器

认证请求和响应DTO

AuthenticationRequest.java

package com.example.orderservice.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
 * 认证请求
 */
@Data
public class AuthenticationRequest {
    
    @NotBlank(message = "Username is required")
    private String username;
    
    @NotBlank(message = "Password is required")
    private String password;
}

AuthenticationResponse.java

package com.example.orderservice.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 认证响应
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthenticationResponse {
    
    private String token;
    private String tokenType = "Bearer";
    private Long expiresIn;
    private String username;
    private String[] roles;
    
    public AuthenticationResponse(String token, Long expiresIn, String username, String[] roles) {
        this.token = token;
        this.expiresIn = expiresIn;
        this.username = username;
        this.roles = roles;
    }
}

认证控制器

AuthController.java

package com.example.orderservice.controller;

import com.example.orderservice.dto.AuthenticationRequest;
import com.example.orderservice.dto.AuthenticationResponse;
import com.example.orderservice.security.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

/**
 * 认证控制器
 */
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
    
    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final JwtTokenUtil jwtTokenUtil;
    
    /**
     * 用户登录
     */
    @PostMapping("/login")
    public ResponseEntity<AuthenticationResponse> login(@Valid @RequestBody AuthenticationRequest request) {
        
        try {
            // 认证用户
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );
            
            // 加载用户详情
            UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
            
            // 生成JWT令牌
            String token = jwtTokenUtil.generateToken(userDetails);
            
            // 获取用户角色
            String[] roles = userDetails.getAuthorities().stream()
                .map(authority -> authority.getAuthority())
                .toArray(String[]::new);
            
            log.info("User {} logged in successfully", request.getUsername());
            
            return ResponseEntity.ok(new AuthenticationResponse(
                token, 
                86400L, // 24小时
                userDetails.getUsername(),
                roles
            ));
            
        } catch (BadCredentialsException e) {
            log.warn("Login failed for user {}: {}", request.getUsername(), e.getMessage());
            return ResponseEntity.status(401).build();
        } catch (Exception e) {
            log.error("Login error for user {}", request.getUsername(), e);
            return ResponseEntity.status(500).build();
        }
    }
    
    /**
     * 刷新令牌
     */
    @PostMapping("/refresh")
    public ResponseEntity<AuthenticationResponse> refreshToken(HttpServletRequest request) {
        
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return ResponseEntity.badRequest().build();
        }
        
        try {
            String token = authHeader.substring(7);
            String username = jwtTokenUtil.getUsernameFromToken(token);
            
            if (username != null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                
                if (jwtTokenUtil.validateToken(token, userDetails)) {
                    String newToken = jwtTokenUtil.refreshToken(token);
                    String[] roles = jwtTokenUtil.getRolesFromToken(newToken);
                    
                    log.info("Token refreshed for user {}", username);
                    
                    return ResponseEntity.ok(new AuthenticationResponse(
                        newToken,
                        86400L,
                        username,
                        roles
                    ));
                }
            }
            
            return ResponseEntity.status(401).build();
            
        } catch (Exception e) {
            log.error("Token refresh failed", e);
            return ResponseEntity.status(500).build();
        }
    }
    
    /**
     * 获取当前用户信息
     */
    @GetMapping("/me")
    public ResponseEntity<Map<String, Object>> getCurrentUser(Authentication authentication) {
        
        if (authentication == null || !authentication.isAuthenticated()) {
            return ResponseEntity.status(401).build();
        }
        
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("username", userDetails.getUsername());
        userInfo.put("roles", userDetails.getAuthorities().stream()
            .map(authority -> authority.getAuthority())
            .toArray(String[]::new));
        userInfo.put("authenticated", true);
        
        return ResponseEntity.ok(userInfo);
    }
    
    /**
     * 用户登出
     */
    @PostMapping("/logout")
    public ResponseEntity<Map<String, String>> logout(HttpServletRequest request) {
        
        // 在实际应用中,可以将令牌加入黑名单
        // 这里只是简单返回成功响应
        
        log.info("User logged out");
        
        Map<String, String> response = new HashMap<>();
        response.put("message", "Logged out successfully");
        
        return ResponseEntity.ok(response);
    }
}

9.3 OAuth2 集成

1. OAuth2 资源服务器配置

OAuth2 配置

OAuth2ResourceServerConfig.java

package com.example.orderservice.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

/**
 * OAuth2 资源服务器配置
 */
@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
    
    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;
    
    /**
     * JWT 解码器
     */
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
    }
    
    /**
     * JWT 认证转换器
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
        authoritiesConverter.setAuthorityPrefix("ROLE_");
        authoritiesConverter.setAuthoritiesClaimName("roles");
        
        JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
        authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        
        return authenticationConverter;
    }
    
    /**
     * OAuth2 安全配置
     */
    @Bean
    public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**", "/actuator/health")
                .permitAll()
                .anyRequest()
                .authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            );
            
        return http.build();
    }
}

2. 服务间认证

Feign 客户端认证配置

FeignClientConfig.java

package com.example.orderservice.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

/**
 * Feign 客户端配置
 */
@Configuration
@Slf4j
public class FeignClientConfig {
    
    /**
     * JWT 令牌传播拦截器
     */
    @Bean
    public RequestInterceptor jwtTokenRelayInterceptor() {
        return new JwtTokenRelayInterceptor();
    }
    
    /**
     * JWT 令牌传播拦截器实现
     */
    public static class JwtTokenRelayInterceptor implements RequestInterceptor {
        
        @Override
        public void apply(RequestTemplate template) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            
            if (authentication instanceof JwtAuthenticationToken) {
                JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) authentication;
                Jwt jwt = jwtToken.getToken();
                
                // 传播JWT令牌
                template.header("Authorization", "Bearer " + jwt.getTokenValue());
                
                log.debug("JWT token propagated to downstream service");
            } else {
                log.warn("No JWT token found in security context for propagation");
            }
        }
    }
}

服务间调用安全

SecureUserServiceClient.java

package com.example.orderservice.client;

import com.example.orderservice.config.FeignClientConfig;
import com.example.orderservice.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * 安全的用户服务客户端
 */
@FeignClient(
    name = "user-service",
    configuration = FeignClientConfig.class
)
public interface SecureUserServiceClient {
    
    /**
     * 获取用户信息(需要认证)
     */
    @GetMapping("/users/{userId}")
    UserDTO getUser(@PathVariable("userId") Long userId);
    
    /**
     * 验证用户(需要认证)
     */
    @GetMapping("/users/{userId}/validate")
    Boolean validateUser(@PathVariable("userId") Long userId);
}

9.4 方法级安全

1. 方法安全注解

安全注解使用

SecureOrderService.java

package com.example.orderservice.service;

import com.example.orderservice.dto.OrderDTO;
import com.example.orderservice.entity.Order;
import com.example.orderservice.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 安全的订单服务
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SecureOrderService {
    
    private final OrderRepository orderRepository;
    
    /**
     * 创建订单(需要USER或ADMIN角色)
     */
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public OrderDTO createOrder(Long userId, Long productId, Integer quantity) {
        
        // 检查用户是否只能为自己创建订单
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String currentUser = auth.getName();
        
        log.info("User {} creating order for userId: {}", currentUser, userId);
        
        // 业务逻辑实现
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setStatus("CREATED");
        order.setCreateTime(System.currentTimeMillis());
        
        order = orderRepository.save(order);
        
        return convertToDTO(order);
    }
    
    /**
     * 获取订单(用户只能查看自己的订单,管理员可以查看所有订单)
     */
    @PreAuthorize("hasRole('ADMIN') or (hasRole('USER') and @securityService.isOrderOwner(#orderId, authentication.name))")
    public OrderDTO getOrder(Long orderId) {
        
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found: " + orderId));
            
        return convertToDTO(order);
    }
    
    /**
     * 获取用户订单列表(用户只能查看自己的订单)
     */
    @PreAuthorize("hasRole('ADMIN') or (hasRole('USER') and @securityService.isCurrentUser(#userId, authentication.name))")
    public List<OrderDTO> getUserOrders(Long userId) {
        
        List<Order> orders = orderRepository.findByUserId(userId);
        
        return orders.stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
    }
    
    /**
     * 删除订单(只有管理员可以删除)
     */
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long orderId) {
        
        if (!orderRepository.existsById(orderId)) {
            throw new RuntimeException("Order not found: " + orderId);
        }
        
        orderRepository.deleteById(orderId);
        log.info("Order {} deleted by admin", orderId);
    }
    
    /**
     * 获取所有订单(只有管理员可以查看)
     */
    @PreAuthorize("hasRole('ADMIN')")
    public List<OrderDTO> getAllOrders() {
        
        List<Order> orders = orderRepository.findAll();
        
        return orders.stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
    }
    
    /**
     * 更新订单状态(返回后检查权限)
     */
    @PostAuthorize("hasRole('ADMIN') or returnObject.userId == @securityService.getCurrentUserId(authentication.name)")
    public OrderDTO updateOrderStatus(Long orderId, String status) {
        
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found: " + orderId));
            
        order.setStatus(status);
        order.setUpdateTime(System.currentTimeMillis());
        
        order = orderRepository.save(order);
        
        return convertToDTO(order);
    }
    
    private OrderDTO convertToDTO(Order order) {
        OrderDTO dto = new OrderDTO();
        dto.setId(order.getId());
        dto.setUserId(order.getUserId());
        dto.setProductId(order.getProductId());
        dto.setQuantity(order.getQuantity());
        dto.setStatus(order.getStatus());
        dto.setCreateTime(order.getCreateTime());
        dto.setUpdateTime(order.getUpdateTime());
        return dto;
    }
}

2. 自定义安全服务

安全服务实现

SecurityService.java

package com.example.orderservice.service;

import com.example.orderservice.entity.Order;
import com.example.orderservice.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 安全服务
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SecurityService {
    
    private final OrderRepository orderRepository;
    private final UserService userService;
    
    /**
     * 检查订单是否属于当前用户
     */
    public boolean isOrderOwner(Long orderId, String username) {
        try {
            Order order = orderRepository.findById(orderId).orElse(null);
            if (order == null) {
                return false;
            }
            
            Long userId = getUserIdByUsername(username);
            boolean isOwner = order.getUserId().equals(userId);
            
            log.debug("Order {} ownership check for user {}: {}", orderId, username, isOwner);
            
            return isOwner;
        } catch (Exception e) {
            log.error("Error checking order ownership", e);
            return false;
        }
    }
    
    /**
     * 检查是否为当前用户
     */
    public boolean isCurrentUser(Long userId, String username) {
        try {
            Long currentUserId = getUserIdByUsername(username);
            boolean isCurrent = userId.equals(currentUserId);
            
            log.debug("Current user check for userId {} and username {}: {}", userId, username, isCurrent);
            
            return isCurrent;
        } catch (Exception e) {
            log.error("Error checking current user", e);
            return false;
        }
    }
    
    /**
     * 获取当前用户ID
     */
    public Long getCurrentUserId(String username) {
        try {
            return getUserIdByUsername(username);
        } catch (Exception e) {
            log.error("Error getting current user ID", e);
            return null;
        }
    }
    
    /**
     * 根据用户名获取用户ID
     */
    private Long getUserIdByUsername(String username) {
        // 这里应该调用用户服务获取用户信息
        // 为了简化,这里使用模拟数据
        if ("admin".equals(username)) {
            return 1L;
        } else if ("user".equals(username)) {
            return 2L;
        } else {
            throw new RuntimeException("User not found: " + username);
        }
    }
    
    /**
     * 检查用户是否有特定权限
     */
    public boolean hasPermission(String username, String permission) {
        try {
            // 这里可以实现复杂的权限检查逻辑
            // 例如:从数据库查询用户权限
            
            log.debug("Permission check for user {} and permission {}", username, permission);
            
            // 简化实现
            if ("admin".equals(username)) {
                return true; // 管理员拥有所有权限
            }
            
            // 根据权限名称进行具体检查
            switch (permission) {
                case "READ_order":
                case "create_order":
                    return true; // 普通用户可以读取和创建订单
                case "delete_order":
                case "admin_operation":
                    return false; // 普通用户不能删除订单或执行管理操作
                default:
                    return false;
            }
        } catch (Exception e) {
            log.error("Error checking permission", e);
            return false;
        }
    }
}

9.5 API 安全防护

1. 请求限流

限流配置

RateLimitingConfig.java

package com.example.orderservice.config;

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 限流配置
 */
@Configuration
public class RateLimitingConfig {
    
    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
    
    /**
     * 创建限流桶
     */
    public Bucket createBucket(String key, long capacity, long refillTokens, Duration refillPeriod) {
        return buckets.computeIfAbsent(key, k -> {
            Bandwidth limit = Bandwidth.classic(capacity, Refill.intervally(refillTokens, refillPeriod));
            return Bucket4j.builder().addLimit(limit).build();
        });
    }
    
    /**
     * 获取用户限流桶
     */
    public Bucket getUserBucket(String userId) {
        return createBucket("user:" + userId, 100, 100, Duration.ofMinutes(1));
    }
    
    /**
     * 获取IP限流桶
     */
    public Bucket getIpBucket(String ip) {
        return createBucket("ip:" + ip, 200, 200, Duration.ofMinutes(1));
    }
    
    /**
     * 获取API限流桶
     */
    public Bucket getApiBucket(String api) {
        return createBucket("api:" + api, 1000, 1000, Duration.ofMinutes(1));
    }
}

限流拦截器

RateLimitingInterceptor.java

package com.example.orderservice.interceptor;

import com.example.orderservice.config.RateLimitingConfig;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 限流拦截器
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class RateLimitingInterceptor implements HandlerInterceptor {
    
    private final RateLimitingConfig rateLimitingConfig;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String clientIp = getClientIp(request);
        String requestUri = request.getRequestURI();
        
        // IP级别限流
        Bucket ipBucket = rateLimitingConfig.getIpBucket(clientIp);
        ConsumptionProbe ipProbe = ipBucket.tryConsumeAndReturnRemaining(1);
        
        if (!ipProbe.isConsumed()) {
            log.warn("Rate limit exceeded for IP: {}", clientIp);
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("{\"error\":\"Rate limit exceeded for IP\"}");
            return false;
        }
        
        // API级别限流
        Bucket apiBucket = rateLimitingConfig.getApiBucket(requestUri);
        ConsumptionProbe apiProbe = apiBucket.tryConsumeAndReturnRemaining(1);
        
        if (!apiProbe.isConsumed()) {
            log.warn("Rate limit exceeded for API: {}", requestUri);
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("{\"error\":\"Rate limit exceeded for API\"}");
            return false;
        }
        
        // 用户级别限流(如果已认证)
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            String username = auth.getName();
            Bucket userBucket = rateLimitingConfig.getUserBucket(username);
            ConsumptionProbe userProbe = userBucket.tryConsumeAndReturnRemaining(1);
            
            if (!userProbe.isConsumed()) {
                log.warn("Rate limit exceeded for user: {}", username);
                response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
                response.getWriter().write("{\"error\":\"Rate limit exceeded for user\"}");
                return false;
            }
            
            // 添加剩余请求数到响应头
            response.setHeader("X-RateLimit-Remaining-User", String.valueOf(userProbe.getRemainingTokens()));
        }
        
        // 添加剩余请求数到响应头
        response.setHeader("X-RateLimit-Remaining-IP", String.valueOf(ipProbe.getRemainingTokens()));
        response.setHeader("X-RateLimit-Remaining-API", String.valueOf(apiProbe.getRemainingTokens()));
        
        return true;
    }
    
    /**
     * 获取客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty()) {
            return xRealIp;
        }
        
        return request.getRemoteAddr();
    }
}

2. 输入验证和防护

输入验证注解

ValidationConfig.java

package com.example.orderservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.Validation;

/**
 * 验证配置
 */
@Configuration
public class ValidationConfig {
    
    @Bean
    public Validator validator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        return factory.getValidator();
    }
}

安全的订单请求DTO

SecureCreateOrderRequest.java

package com.example.orderservice.dto;

import lombok.Data;

import javax.validation.constraints.*;

/**
 * 安全的创建订单请求
 */
@Data
public class SecureCreateOrderRequest {
    
    @NotNull(message = "User ID is required")
    @Positive(message = "User ID must be positive")
    private Long userId;
    
    @NotNull(message = "Product ID is required")
    @Positive(message = "Product ID must be positive")
    private Long productId;
    
    @NotNull(message = "Quantity is required")
    @Min(value = 1, message = "Quantity must be at least 1")
    @Max(value = 100, message = "Quantity cannot exceed 100")
    private Integer quantity;
    
    @Size(max = 500, message = "Notes cannot exceed 500 characters")
    @Pattern(regexp = "^[a-zA-Z0-9\\s\\-_.,!?]*$", message = "Notes contain invalid characters")
    private String notes;
}

安全控制器

SecureOrderController.java

package com.example.orderservice.controller;

import com.example.orderservice.dto.SecureCreateOrderRequest;
import com.example.orderservice.dto.OrderDTO;
import com.example.orderservice.service.SecureOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.util.List;

/**
 * 安全的订单控制器
 */
@RestController
@RequestMapping("/secure/orders")
@RequiredArgsConstructor
@Validated
@Slf4j
public class SecureOrderController {
    
    private final SecureOrderService orderService;
    
    /**
     * 创建订单
     */
    @PostMapping
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public ResponseEntity<OrderDTO> createOrder(@Valid @RequestBody SecureCreateOrderRequest request,
                                               Authentication authentication) {
        
        log.info("Creating order for user: {}", authentication.getName());
        
        // 安全检查:用户只能为自己创建订单(除非是管理员)
        if (!authentication.getAuthorities().stream()
                .anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"))) {
            
            // 这里应该验证request.getUserId()是否与当前认证用户匹配
            // 为了简化,假设已经验证过
        }
        
        OrderDTO order = orderService.createOrder(
            request.getUserId(),
            request.getProductId(),
            request.getQuantity()
        );
        
        return ResponseEntity.ok(order);
    }
    
    /**
     * 获取订单详情
     */
    @GetMapping("/{orderId}")
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public ResponseEntity<OrderDTO> getOrder(@PathVariable @Positive Long orderId) {
        
        OrderDTO order = orderService.getOrder(orderId);
        return ResponseEntity.ok(order);
    }
    
    /**
     * 获取用户订单列表
     */
    @GetMapping("/user/{userId}")
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public ResponseEntity<List<OrderDTO>> getUserOrders(@PathVariable @Positive Long userId) {
        
        List<OrderDTO> orders = orderService.getUserOrders(userId);
        return ResponseEntity.ok(orders);
    }
    
    /**
     * 删除订单(仅管理员)
     */
    @DeleteMapping("/{orderId}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteOrder(@PathVariable @Positive Long orderId) {
        
        orderService.deleteOrder(orderId);
        return ResponseEntity.noContent().build();
    }
    
    /**
     * 获取所有订单(仅管理员)
     */
    @GetMapping("/admin/all")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<List<OrderDTO>> getAllOrders() {
        
        List<OrderDTO> orders = orderService.getAllOrders();
        return ResponseEntity.ok(orders);
    }
}

3. 安全响应处理

全局异常处理器

SecurityExceptionHandler.java

package com.example.orderservice.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;

/**
 * 安全异常处理器
 */
@RestControllerAdvice
@Slf4j
public class SecurityExceptionHandler {
    
    /**
     * 认证异常
     */
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Map<String, Object>> handleAuthenticationException(AuthenticationException e) {
        log.warn("Authentication failed: {}", e.getMessage());
        
        Map<String, Object> response = new HashMap<>();
        response.put("error", "Authentication failed");
        response.put("message", "Invalid credentials");
        response.put("status", HttpStatus.UNAUTHORIZED.value());
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
    }
    
    /**
     * 授权异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<Map<String, Object>> handleAccessDeniedException(AccessDeniedException e) {
        log.warn("Access denied: {}", e.getMessage());
        
        Map<String, Object> response = new HashMap<>();
        response.put("error", "Access denied");
        response.put("message", "Insufficient permissions");
        response.put("status", HttpStatus.FORBIDDEN.value());
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
    }
    
    /**
     * 凭证异常
     */
    @ExceptionHandler(BadCredentialsException.class)
    public ResponseEntity<Map<String, Object>> handleBadCredentialsException(BadCredentialsException e) {
        log.warn("Bad credentials: {}", e.getMessage());
        
        Map<String, Object> response = new HashMap<>();
        response.put("error", "Invalid credentials");
        response.put("message", "Username or password is incorrect");
        response.put("status", HttpStatus.UNAUTHORIZED.value());
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
    }
    
    /**
     * 参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationException(MethodArgumentNotValidException e) {
        log.warn("Validation failed: {}", e.getMessage());
        
        Map<String, Object> response = new HashMap<>();
        Map<String, String> errors = new HashMap<>();
        
        e.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        response.put("error", "Validation failed");
        response.put("message", "Invalid input parameters");
        response.put("details", errors);
        response.put("status", HttpStatus.BAD_REQUEST.value());
        response.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }
}

9.6 安全监控与审计

1. 安全事件监听

安全事件监听器

SecurityEventListener.java

package com.example.orderservice.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.*;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.stereotype.Component;

/**
 * 安全事件监听器
 */
@Component
@Slf4j
public class SecurityEventListener {
    
    /**
     * 认证成功事件
     */
    @EventListener
    public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
        String username = event.getAuthentication().getName();
        String authorities = event.getAuthentication().getAuthorities().toString();
        
        log.info("Authentication successful - User: {}, Authorities: {}", username, authorities);
        
        // 记录到审计日志
        auditLog("AUTHENTICATION_SUCCESS", username, "User logged in successfully");
    }
    
    /**
     * 认证失败事件
     */
    @EventListener
    public void handleAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
        String username = event.getAuthentication().getName();
        String reason = event.getException().getMessage();
        
        log.warn("Authentication failed - User: {}, Reason: {}", username, reason);
        
        // 记录到审计日志
        auditLog("AUTHENTICATION_FAILURE", username, "Authentication failed: " + reason);
    }
    
    /**
     * 授权拒绝事件
     */
    @EventListener
    public void handleAuthorizationDenied(AuthorizationDeniedEvent event) {
        String username = event.getAuthentication().getName();
        String resource = event.getAuthorizationDecision().toString();
        
        log.warn("Authorization denied - User: {}, Resource: {}", username, resource);
        
        // 记录到审计日志
        auditLog("AUTHORIZATION_DENIED", username, "Access denied to resource: " + resource);
    }
    
    /**
     * 记录审计日志
     */
    private void auditLog(String eventType, String username, String description) {
        // 这里可以将审计日志发送到专门的审计系统
        // 例如:ELK Stack、Splunk等
        
        log.info("AUDIT: {} - User: {} - Description: {} - Timestamp: {}", 
                eventType, username, description, System.currentTimeMillis());
    }
}

2. 安全指标监控

安全指标收集器

SecurityMetricsCollector.java

package com.example.orderservice.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 安全指标收集器
 */
@Component
@RequiredArgsConstructor
public class SecurityMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    
    // 认证相关指标
    private final Counter authenticationSuccessCounter;
    private final Counter authenticationFailureCounter;
    private final Timer authenticationTimer;
    
    // 授权相关指标
    private final Counter authorizationSuccessCounter;
    private final Counter authorizationFailureCounter;
    
    // 安全事件指标
    private final Counter securityViolationCounter;
    private final Counter rateLimitExceededCounter;
    
    public SecurityMetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // 初始化计数器
        this.authenticationSuccessCounter = Counter.builder("security.authentication.success")
            .description("Number of successful authentications")
            .register(meterRegistry);
            
        this.authenticationFailureCounter = Counter.builder("security.authentication.failure")
            .description("Number of failed authentications")
            .register(meterRegistry);
            
        this.authenticationTimer = Timer.builder("security.authentication.duration")
            .description("Authentication processing time")
            .register(meterRegistry);
            
        this.authorizationSuccessCounter = Counter.builder("security.authorization.success")
            .description("Number of successful authorizations")
            .register(meterRegistry);
            
        this.authorizationFailureCounter = Counter.builder("security.authorization.failure")
            .description("Number of failed authorizations")
            .register(meterRegistry);
            
        this.securityViolationCounter = Counter.builder("security.violation")
            .description("Number of security violations")
            .register(meterRegistry);
            
        this.rateLimitExceededCounter = Counter.builder("security.ratelimit.exceeded")
            .description("Number of rate limit violations")
            .register(meterRegistry);
    }
    
    /**
     * 记录认证成功
     */
    public void recordAuthenticationSuccess() {
        authenticationSuccessCounter.increment();
    }
    
    /**
     * 记录认证失败
     */
    public void recordAuthenticationFailure(String reason) {
        authenticationFailureCounter.increment(
            "reason", reason
        );
    }
    
    /**
     * 记录认证耗时
     */
    public Timer.Sample startAuthenticationTimer() {
        return Timer.start(meterRegistry);
    }
    
    public void stopAuthenticationTimer(Timer.Sample sample) {
        sample.stop(authenticationTimer);
    }
    
    /**
     * 记录授权成功
     */
    public void recordAuthorizationSuccess() {
        authorizationSuccessCounter.increment();
    }
    
    /**
     * 记录授权失败
     */
    public void recordAuthorizationFailure(String resource) {
        authorizationFailureCounter.increment(
            "resource", resource
        );
    }
    
    /**
     * 记录安全违规
     */
    public void recordSecurityViolation(String violationType) {
        securityViolationCounter.increment(
            "type", violationType
        );
    }
    
    /**
     * 记录限流超限
     */
    public void recordRateLimitExceeded(String limitType) {
        rateLimitExceededCounter.increment(
            "type", limitType
        );
    }
}

9.7 配置文件

application.yml 安全配置

# 应用配置
spring:
  application:
    name: order-service
  
  # 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useSSL=true&serverTimezone=UTC
    username: ${DB_USERNAME:order_user}
    password: ${DB_PASSWORD:order_pass}
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        format_sql: true
  
  # Redis配置(用于会话存储)
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
  
  # Security配置
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${JWT_JWK_SET_URI:http://localhost:8080/auth/realms/microservices/protocol/openid_connect/certs}
          issuer-uri: ${JWT_ISSUER_URI:http://localhost:8080/auth/realms/microservices}

# JWT配置
jwt:
  secret: ${JWT_SECRET:myVerySecretKeyThatIsAtLeast256BitsLongForHS512Algorithm}
  expiration: ${JWT_EXPIRATION:86400} # 24小时
  refresh-expiration: ${JWT_REFRESH_EXPIRATION:604800} # 7天

# 服务发现配置
eureka:
  client:
    service-url:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka/}
  instance:
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90

# 监控配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized
      roles: ADMIN
  metrics:
    export:
      prometheus:
        enabled: true

# 日志配置
logging:
  level:
    com.example.orderservice: INFO
    org.springframework.security: DEBUG
    org.springframework.web: INFO
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId},%X{spanId}] %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId},%X{spanId}] %logger{36} - %msg%n"
  file:
    name: logs/order-service.log
    max-size: 100MB
    max-history: 30

# 限流配置
rate-limit:
  enabled: true
  default-capacity: 100
  default-refill-tokens: 100
  default-refill-period: 60 # 秒
  
  # 不同类型的限流配置
  user:
    capacity: 100
    refill-tokens: 100
    refill-period: 60
  
  ip:
    capacity: 200
    refill-tokens: 200
    refill-period: 60
  
  api:
    capacity: 1000
    refill-tokens: 1000
    refill-period: 60

# 安全配置
security:
  # CORS配置
  cors:
    allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
    allowed-methods: GET,POST,PUT,DELETE,OPTIONS
    allowed-headers: "*"
    allow-credentials: true
    max-age: 3600
  
  # 内容安全策略
  content-security-policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
  
  # 其他安全头
  headers:
    frame-options: DENY
    content-type-options: nosniff
    xss-protection: "1; mode=block"
    referrer-policy: strict-origin-when-cross-origin

9.8 总结

核心概念回顾

  1. 微服务安全挑战

    • 安全边界扩大
    • 身份认证复杂
    • 授权控制分散
    • 网络攻击威胁
  2. 安全架构原则

    • 零信任架构
    • 深度防御
    • 持续监控
    • 最小权限原则
  3. Spring Security集成

    • 基础安全配置
    • JWT认证实现
    • OAuth2资源服务器
    • 方法级安全控制
  4. API安全防护

    • 请求限流
    • 输入验证
    • 安全响应处理
    • 异常统一处理

最佳实践

  1. 认证授权

    • 使用强密码策略
    • 实施多因素认证
    • 定期轮换密钥
    • 最小权限分配
  2. 通信安全

    • 强制HTTPS通信
    • 实施mTLS认证
    • 加密敏感数据
    • 验证证书有效性
  3. 输入验证

    • 严格参数验证
    • 防止注入攻击
    • 限制输入长度
    • 过滤特殊字符
  4. 监控审计

    • 记录安全事件
    • 监控异常行为
    • 定期安全审计
    • 及时响应威胁

注意事项

  1. 性能影响

    • 安全检查开销
    • 加密解密耗时
    • 网络延迟增加
    • 资源消耗增长
  2. 配置管理

    • 密钥安全存储
    • 环境隔离配置
    • 配置版本控制
    • 敏感信息保护
  3. 兼容性考虑

    • 客户端适配
    • 协议版本兼容
    • 浏览器支持
    • 移动端适配
  4. 运维管理

    • 证书管理
    • 密钥轮换
    • 安全更新
    • 应急响应

扩展方向

  1. 高级功能

    • 动态权限管理
    • 行为分析检测
    • 威胁情报集成
    • 自适应安全
  2. 集成组件

    • Keycloak身份管理
    • Vault密钥管理
    • Istio服务网格
    • Falco运行时安全
  3. 合规要求

    • GDPR数据保护
    • SOX财务合规
    • HIPAA医疗合规
    • PCI DSS支付合规

通过本章的学习,我们掌握了微服务安全的核心技术和实践方法。下一章我们将学习微服务的部署与运维,了解如何在生产环境中安全可靠地运行微服务系统。”, System.currentTimeMillis());

    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

/**
 * 约束违反异常
 */
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Map<String, Object>> handleConstraintViolationException(ConstraintViolationException e) {
    log.warn("Constraint violation: {}", e.getMessage());

    Map<String, Object> response = new HashMap<>();
    response.put("error", "Constraint violation");
    response.put("message", e.getMessage());
    response.put("status", HttpStatus.BAD_REQUEST.value());
    response.put("timestamp