9.1 微服务安全概述
1. 微服务安全挑战
微服务架构相比单体应用在安全方面面临更多挑战:
安全边界扩大
网络边界增多:
- 服务间通信增加攻击面
- 内部网络不再安全
- 需要端到端加密
身份认证复杂:
- 多服务身份管理
- 服务间信任关系
- 用户身份传播
授权控制分散:
- 细粒度权限控制
- 跨服务授权
- 动态权限管理
主要安全威胁
网络攻击:
- 中间人攻击
- 网络窃听
- DDoS攻击
身份伪造:
- 服务身份伪造
- 用户身份盗用
- 令牌劫持
权限滥用:
- 权限提升
- 越权访问
- 数据泄露
2. 安全架构设计原则
零信任架构
永不信任,始终验证:
- 不信任网络位置
- 验证每个请求
- 最小权限原则
深度防御:
- 多层安全控制
- 冗余安全机制
- 故障安全设计
持续监控:
- 实时安全监控
- 异常行为检测
- 安全事件响应
安全设计模式
网关安全模式:
- 统一入口控制
- 集中认证授权
- 流量监控过滤
令牌传播模式:
- JWT令牌传递
- 上下文传播
- 令牌刷新机制
服务网格安全:
- 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 总结
核心概念回顾
微服务安全挑战:
- 安全边界扩大
- 身份认证复杂
- 授权控制分散
- 网络攻击威胁
安全架构原则:
- 零信任架构
- 深度防御
- 持续监控
- 最小权限原则
Spring Security集成:
- 基础安全配置
- JWT认证实现
- OAuth2资源服务器
- 方法级安全控制
API安全防护:
- 请求限流
- 输入验证
- 安全响应处理
- 异常统一处理
最佳实践
认证授权:
- 使用强密码策略
- 实施多因素认证
- 定期轮换密钥
- 最小权限分配
通信安全:
- 强制HTTPS通信
- 实施mTLS认证
- 加密敏感数据
- 验证证书有效性
输入验证:
- 严格参数验证
- 防止注入攻击
- 限制输入长度
- 过滤特殊字符
监控审计:
- 记录安全事件
- 监控异常行为
- 定期安全审计
- 及时响应威胁
注意事项
性能影响:
- 安全检查开销
- 加密解密耗时
- 网络延迟增加
- 资源消耗增长
配置管理:
- 密钥安全存储
- 环境隔离配置
- 配置版本控制
- 敏感信息保护
兼容性考虑:
- 客户端适配
- 协议版本兼容
- 浏览器支持
- 移动端适配
运维管理:
- 证书管理
- 密钥轮换
- 安全更新
- 应急响应
扩展方向
高级功能:
- 动态权限管理
- 行为分析检测
- 威胁情报集成
- 自适应安全
集成组件:
- Keycloak身份管理
- Vault密钥管理
- Istio服务网格
- Falco运行时安全
合规要求:
- 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