跳转至

高级主题与最佳实践

目录


多租户认证

什么是多租户?

多租户(Multi-Tenancy) 是一种软件架构,单个应用实例可以服务多个租户(客户),每个租户的数据相互隔离。

多租户隔离策略

  1. 数据库级隔离:每个租户独立数据库
  2. Schema级隔离:共享数据库,独立Schema
  3. 表级隔离:共享表,通过tenant_id字段区分

实现多租户认证

方式1:基于子域名

tenant1.example.com → 租户1
tenant2.example.com → 租户2
/**
 * 租户识别器
 */
@Component
public class TenantIdentifierResolver {

    /**
     * 从请求中提取租户标识
     */
    public String resolveTenantIdentifier(HttpServletRequest request) {
        // 方式1: 从子域名提取
        String host = request.getServerName();
        if (host.contains(".")) {
            String subdomain = host.substring(0, host.indexOf("."));
            if (!subdomain.equals("www")) {
                return subdomain;
            }
        }

        // 方式2: 从请求头提取
        String tenantHeader = request.getHeader("X-Tenant-ID");
        if (tenantHeader != null) {
            return tenantHeader;
        }

        // 方式3: 从JWT Token提取
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getCredentials() instanceof Jwt) {
            Jwt jwt = (Jwt) auth.getCredentials();
            return jwt.getClaimAsString("tenant_id");
        }

        return "default";
    }
}

方式2:基于JWT Token

/**
 * 多租户JWT Token Provider
 */
@Component
public class MultiTenantJwtTokenProvider {

    @Autowired
    private TenantService tenantService;

    /**
     * 生成包含租户信息的Token
     */
    public String generateToken(Authentication authentication, String tenantId) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();

        // 验证用户是否属于该租户
        if (!tenantService.userBelongsToTenant(userDetails.getUsername(), tenantId)) {
            throw new IllegalArgumentException("User does not belong to tenant");
        }

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + 3600000);

        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .claim("tenant_id", tenantId)
            .claim("tenant_name", tenantService.getTenantName(tenantId))
            .claim("authorities", userDetails.getAuthorities())
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(key, SignatureAlgorithm.HS512)
            .compact();
    }
}

数据隔离

/**
 * 租户上下文
 */
public class TenantContext {

    private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();

    public static void setTenantId(String tenantId) {
        CURRENT_TENANT.set(tenantId);
    }

    public static String getTenantId() {
        return CURRENT_TENANT.get();
    }

    public static void clear() {
        CURRENT_TENANT.remove();
    }
}

/**
 * 租户识别过滤器
 */
@Component
public class TenantIdentifierFilter extends OncePerRequestFilter {

    @Autowired
    private TenantIdentifierResolver tenantIdentifierResolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain filterChain) 
            throws ServletException, IOException {

        try {
            String tenantId = tenantIdentifierResolver.resolveTenantIdentifier(request);
            TenantContext.setTenantId(tenantId);
            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
}

/**
 * JPA数据过滤
 */
@Entity
@Table(name = "orders")
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = "string"))
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "tenant_id", nullable = false)
    private String tenantId;

    // 其他字段...
}

/**
 * 自动设置租户ID
 */
@Component
public class TenantEntityListener {

    @PrePersist
    public void setTenantId(Object entity) {
        if (entity instanceof TenantAware) {
            TenantAware tenantAware = (TenantAware) entity;
            if (tenantAware.getTenantId() == null) {
                tenantAware.setTenantId(TenantContext.getTenantId());
            }
        }
    }
}

分布式会话管理

为什么需要分布式会话?

在微服务或集群环境中,用户请求可能被路由到不同服务器,需要共享会话数据。

解决方案对比

方案 优点 缺点 适用场景
Sticky Session 简单,无需额外存储 单点故障,负载不均 小规模应用
Session复制 高可用 网络开销大,不适合大集群 中小规模
集中式存储 扩展性好,高可用 需要额外组件 大规模生产环境
JWT Token 无状态,易扩展 无法主动吊销 微服务、API

使用Redis实现分布式会话

Spring Session + Redis

<dependencies>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>
/**
 * Spring Session配置
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)  // 30分钟
public class RedisSessionConfig {

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName("localhost");
        config.setPort(6379);
        config.setPassword("password");

        return new LettuceConnectionFactory(config);
    }

    /**
     * Session序列化器
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    /**
     * Cookie序列化策略
     */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("SESSIONID");
        serializer.setCookiePath("/");
        serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");  // 跨子域
        serializer.setUseHttpOnlyCookie(true);
        serializer.setUseSecureCookie(true);
        serializer.setSameSite("Lax");
        return serializer;
    }
}
# application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.session.store-type=redis
spring.session.redis.namespace=spring:session

自定义Session存储

/**
 * 自定义Session Repository
 */
@Component
public class CustomSessionRepository implements SessionRepository<CustomSession> {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String SESSION_PREFIX = "session:";
    private static final int DEFAULT_MAX_INACTIVE_INTERVAL = 1800;

    @Override
    public CustomSession createSession() {
        CustomSession session = new CustomSession();
        session.setId(UUID.randomUUID().toString());
        session.setMaxInactiveInterval(Duration.ofSeconds(DEFAULT_MAX_INACTIVE_INTERVAL));
        session.setCreationTime(Instant.now());
        session.setLastAccessedTime(Instant.now());
        return session;
    }

    @Override
    public void save(CustomSession session) {
        String key = SESSION_PREFIX + session.getId();
        session.setLastAccessedTime(Instant.now());

        redisTemplate.opsForHash().putAll(key, session.toMap());
        redisTemplate.expire(key, session.getMaxInactiveInterval());
    }

    @Override
    public CustomSession findById(String id) {
        String key = SESSION_PREFIX + id;
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);

        if (entries.isEmpty()) {
            return null;
        }

        CustomSession session = CustomSession.fromMap(entries);

        // 检查是否过期
        if (session.isExpired()) {
            deleteById(id);
            return null;
        }

        return session;
    }

    @Override
    public void deleteById(String id) {
        String key = SESSION_PREFIX + id;
        redisTemplate.delete(key);
    }
}

Session活动监控

/**
 * Session活动监控
 */
@Component
public class SessionActivityMonitor {

    @Autowired
    private SessionRegistry sessionRegistry;

    /**
     * 获取活跃会话数
     */
    public int getActiveSessionCount() {
        return sessionRegistry.getAllPrincipals().stream()
            .mapToInt(principal -> sessionRegistry.getAllSessions(principal, false).size())
            .sum();
    }

    /**
     * 获取用户会话
     */
    public List<SessionInfo> getUserSessions(String username) {
        List<SessionInfo> sessions = new ArrayList<>();

        for (Object principal : sessionRegistry.getAllPrincipals()) {
            if (principal.toString().equals(username)) {
                List<SessionInformation> userSessions = 
                    sessionRegistry.getAllSessions(principal, false);

                for (SessionInformation si : userSessions) {
                    SessionInfo info = new SessionInfo();
                    info.setSessionId(si.getSessionId());
                    info.setLastRequest(si.getLastRequest());
                    info.setExpired(si.isExpired());
                    sessions.add(info);
                }
            }
        }

        return sessions;
    }

    /**
     * 定期清理过期会话
     */
    @Scheduled(fixedRate = 60000)  // 每分钟
    public void cleanupExpiredSessions() {
        for (Object principal : sessionRegistry.getAllPrincipals()) {
            List<SessionInformation> sessions = 
                sessionRegistry.getAllSessions(principal, true);  // 包含过期

            for (SessionInformation si : sessions) {
                if (si.isExpired()) {
                    sessionRegistry.removeSessionInformation(si.getSessionId());
                }
            }
        }
    }
}

微服务安全架构

架构模式

1. 边缘认证模式(Gateway Authentication)

                          ┌──────────────┐
User ──────────────────→  │  API Gateway │  ← JWT验证
                          └──────┬───────┘
                    ┌────────────┼────────────┐
                    │            │            │
                ┌───▼───┐    ┌───▼───┐   ┌───▼───┐
                │Service│    │Service│   │Service│
                │   A   │    │   B   │   │   C   │
                └───────┘    └───────┘   └───────┘

特点: - 认证在网关统一处理 - 内部服务信任网关 - 简化服务开发

实现:

/**
 * Gateway JWT过滤器
 */
@Component
public class GatewayJwtFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 公开路径跳过认证
        if (isPublicPath(request.getPath().value())) {
            return chain.filter(exchange);
        }

        // 提取并验证JWT
        String token = extractToken(request);
        if (token == null || !tokenProvider.validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        // 从Token提取用户信息,添加到请求头传递给后端服务
        String username = tokenProvider.getUsernameFromToken(token);
        String userId = tokenProvider.getClaimFromToken(token, "user_id");

        ServerHttpRequest modifiedRequest = request.mutate()
            .header("X-User-Id", userId)
            .header("X-Username", username)
            .build();

        return chain.filter(exchange.mutate().request(modifiedRequest).build());
    }

    @Override
    public int getOrder() {
        return -100;  // 高优先级
    }
}

/**
 * 后端服务获取用户信息
 */
@Component
public class UserContextInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                            Object handler) {
        String userId = request.getHeader("X-User-Id");
        String username = request.getHeader("X-Username");

        // 设置到上下文
        UserContext.setUserId(userId);
        UserContext.setUsername(username);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, Exception ex) {
        UserContext.clear();
    }
}

2. 服务间认证模式(Service-to-Service Authentication)

Service A ──────────────→ Service B
          Client Credentials
          (OAuth2)

实现:

/**
 * 服务间调用客户端
 */
@Configuration
public class ServiceClientConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(OAuth2AuthorizedClientManager clientManager) {
        RestTemplate restTemplate = new RestTemplate();

        // 添加OAuth2拦截器
        restTemplate.getInterceptors().add((request, body, execution) -> {
            OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
                .withClientRegistrationId("service-client")
                .principal("service-account")
                .build();

            OAuth2AuthorizedClient client = clientManager.authorize(authorizeRequest);
            if (client != null) {
                request.getHeaders().setBearerAuth(
                    client.getAccessToken().getTokenValue()
                );
            }

            return execution.execute(request, body);
        });

        return restTemplate;
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        DefaultOAuth2AuthorizedClientManager clientManager = 
            new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository,
                authorizedClientRepository
            );

        clientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return clientManager;
    }
}
# 服务配置
spring:
  security:
    oauth2:
      client:
        registration:
          service-client:
            client-id: service-a
            client-secret: service-a-secret
            authorization-grant-type: client_credentials
            scope: service.read,service.write
        provider:
          auth-server:
            token-uri: http://auth-server:9000/oauth2/token

分布式追踪与安全

/**
 * 在分布式追踪中记录安全信息
 */
@Component
public class SecurityTracingAspect {

    @Autowired
    private Tracer tracer;

    @Around("@annotation(PreAuthorize)")
    public Object traceSecurityCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        Span span = tracer.currentSpan();

        if (span != null) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null) {
                span.tag("security.user", auth.getName());
                span.tag("security.authorities", 
                    auth.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority)
                        .collect(Collectors.joining(",")));
            }
        }

        return joinPoint.proceed();
    }
}

性能优化策略

1. 缓存认证信息

/**
 * 缓存UserDetails
 */
@Service
public class CachedUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Cacheable(value = "userDetails", key = "#username")
    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return buildUserDetails(user);
    }

    @CacheEvict(value = "userDetails", key = "#username")
    public void evictUserCache(String username) {
        // 缓存失效(用户信息更新时调用)
    }
}

2. 批量权限检查

/**
 * 批量权限检查
 */
@Service
public class BatchAuthorizationService {

    @Autowired
    private PermissionRepository permissionRepository;

    /**
     * 批量检查权限
     */
    public Map<String, Boolean> checkPermissionsBatch(String userId, List<String> permissions) {
        // 一次查询获取用户所有权限
        Set<String> userPermissions = permissionRepository.findByUserId(userId);

        // 批量判断
        return permissions.stream()
            .collect(Collectors.toMap(
                permission -> permission,
                permission -> userPermissions.contains(permission)
            ));
    }
}

3. JWT签名算法选择

性能对比(相对速度):
HMAC (HS256):     ⭐⭐⭐⭐⭐ (最快)
ECDSA (ES256):     ⭐⭐⭐⭐
RSA (RS256):       ⭐⭐⭐

安全性对比:
ECDSA (ES256):     ⭐⭐⭐⭐⭐ (相同密钥长度下最安全)
RSA (RS256):       ⭐⭐⭐⭐
HMAC (HS256):      ⭐⭐⭐ (需要安全传输密钥)

推荐:
- 内部系统:HMAC (HS256)
- 公开API:ECDSA (ES256) 或 RSA (RS256)

4. 数据库查询优化

/**
 * 优化用户权限查询
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // ❌ 错误:N+1查询问题
    @Query("SELECT u FROM User u WHERE u.username = :username")
    User findByUsername(String username);

    // ✅ 正确:使用JOIN FETCH一次性加载
    @Query("SELECT u FROM User u " +
           "LEFT JOIN FETCH u.roles r " +
           "LEFT JOIN FETCH r.permissions " +
           "WHERE u.username = :username")
    User findByUsernameWithRolesAndPermissions(@Param("username") String username);

    // 或使用EntityGraph
    @EntityGraph(attributePaths = {"roles", "roles.permissions"})
    Optional<User> findWithRolesByUsername(String username);
}

5. 连接池配置

# HikariCP配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# Redis连接池
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5

审计日志与监控

审计日志

/**
 * 审计事件
 */
@Entity
@Table(name = "audit_logs")
@Data
public class AuditLog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String action;  // LOGIN, LOGOUT, CREATE, UPDATE, DELETE

    private String resource;  // users, orders, etc.

    private String resourceId;

    private String ipAddress;

    private String userAgent;

    private boolean success;

    private String failureReason;

    @Column(columnDefinition = "TEXT")
    private String details;  // JSON格式的详细信息

    @CreationTimestamp
    private LocalDateTime timestamp;
}

/**
 * 审计服务
 */
@Service
@Async
public class AuditService {

    @Autowired
    private AuditLogRepository auditLogRepository;

    /**
     * 记录登录事件
     */
    public void logLogin(String username, String ipAddress, boolean success, String failureReason) {
        AuditLog log = new AuditLog();
        log.setUsername(username);
        log.setAction("LOGIN");
        log.setIpAddress(ipAddress);
        log.setSuccess(success);
        log.setFailureReason(failureReason);

        auditLogRepository.save(log);
    }

    /**
     * 记录资源访问
     */
    public void logResourceAccess(String username, String action, String resource, 
                                  String resourceId, boolean success) {
        AuditLog log = new AuditLog();
        log.setUsername(username);
        log.setAction(action);
        log.setResource(resource);
        log.setResourceId(resourceId);
        log.setSuccess(success);

        auditLogRepository.save(log);
    }
}

/**
 * 审计切面
 */
@Aspect
@Component
public class AuditAspect {

    @Autowired
    private AuditService auditService;

    @AfterReturning(
        pointcut = "@annotation(audited)",
        returning = "result"
    )
    public void auditSuccess(JoinPoint joinPoint, Audited audited, Object result) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth != null ? auth.getName() : "anonymous";

        auditService.logResourceAccess(
            username,
            audited.action(),
            audited.resource(),
            extractResourceId(joinPoint, result),
            true
        );
    }

    @AfterThrowing(
        pointcut = "@annotation(audited)",
        throwing = "ex"
    )
    public void auditFailure(JoinPoint joinPoint, Audited audited, Exception ex) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth != null ? auth.getName() : "anonymous";

        auditService.logResourceAccess(
            username,
            audited.action(),
            audited.resource(),
            null,
            false
        );
    }
}

/**
 * 审计注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
    String action();
    String resource();
}

/**
 * 使用示例
 */
@Service
public class UserService {

    @Audited(action = "CREATE", resource = "users")
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Audited(action = "DELETE", resource = "users")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

监控指标

/**
 * 安全监控指标
 */
@Component
public class SecurityMetrics {

    private final Counter loginAttempts;
    private final Counter loginFailures;
    private final Counter loginSuccesses;
    private final Counter accessDenied;
    private final Gauge activeUsers;

    public SecurityMetrics(MeterRegistry registry) {
        this.loginAttempts = Counter.builder("security.login.attempts")
            .description("Total login attempts")
            .register(registry);

        this.loginFailures = Counter.builder("security.login.failures")
            .tag("reason", "invalid_credentials")
            .description("Failed login attempts")
            .register(registry);

        this.loginSuccesses = Counter.builder("security.login.successes")
            .description("Successful logins")
            .register(registry);

        this.accessDenied = Counter.builder("security.access.denied")
            .description("Access denied events")
            .register(registry);

        this.activeUsers = Gauge.builder("security.users.active", this, SecurityMetrics::getActiveUserCount)
            .description("Number of active users")
            .register(registry);
    }

    public void recordLoginAttempt() {
        loginAttempts.increment();
    }

    public void recordLoginSuccess() {
        loginSuccesses.increment();
    }

    public void recordLoginFailure() {
        loginFailures.increment();
    }

    public void recordAccessDenied() {
        accessDenied.increment();
    }

    private double getActiveUserCount() {
        // 实现活跃用户数统计
        return sessionRegistry.getAllPrincipals().size();
    }
}

安全告警

/**
 * 安全告警服务
 */
@Service
public class SecurityAlertService {

    @Autowired
    private NotificationService notificationService;

    @Autowired
    private AuditLogRepository auditLogRepository;

    /**
     * 检测暴力破解攻击
     */
    @Scheduled(fixedRate = 60000)  // 每分钟
    public void detectBruteForceAttacks() {
        LocalDateTime oneMinuteAgo = LocalDateTime.now().minusMinutes(1);

        // 查找1分钟内失败次数超过5次的IP
        List<Map<String, Object>> suspiciousIps = auditLogRepository
            .findFailedLoginsByIpSince(oneMinuteAgo, 5);

        for (Map<String, Object> entry : suspiciousIps) {
            String ipAddress = (String) entry.get("ipAddress");
            Long failureCount = (Long) entry.get("failureCount");

            notificationService.sendAlert(
                "可疑的暴力破解攻击",
                String.format("IP %s 在1分钟内尝试登录失败 %d 次", ipAddress, failureCount)
            );

            // 可以考虑自动封禁IP
            // ipBlocklistService.blockIp(ipAddress, Duration.ofHours(1));
        }
    }

    /**
     * 检测异常访问模式
     */
    @Scheduled(fixedRate = 300000)  // 每5分钟
    public void detectAnomalousAccess() {
        LocalDateTime fiveMinutesAgo = LocalDateTime.now().minusMinutes(5);

        // 检测短时间内大量资源访问
        List<Map<String, Object>> heavyUsers = auditLogRepository
            .findUsersWithHighActivitySince(fiveMinutesAgo, 100);

        for (Map<String, Object> entry : heavyUsers) {
            String username = (String) entry.get("username");
            Long requestCount = (Long) entry.get("requestCount");

            notificationService.sendAlert(
                "异常访问模式",
                String.format("用户 %s 在5分钟内发起了 %d 次请求", username, requestCount)
            );
        }
    }
}

安全最佳实践汇总

1. 密码安全

/**
 * 密码安全最佳实践
 */
public class PasswordSecurityBestPractices {

    // ✅ 使用强加密算法
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);  // strength 12
    }

    // ✅ 密码复杂度验证
    public boolean validatePasswordStrength(String password) {
        return password.length() >= 12 &&
               password.matches(".*[A-Z].*") &&
               password.matches(".*[a-z].*") &&
               password.matches(".*[0-9].*") &&
               password.matches(".*[!@#$%^&*].*");
    }

    // ✅ 密码历史记录(防止重用)
    public boolean checkPasswordHistory(String userId, String newPassword) {
        List<String> previousPasswords = passwordHistoryRepository
            .findLastNPasswords(userId, 5);

        return previousPasswords.stream()
            .noneMatch(oldPassword -> 
                passwordEncoder.matches(newPassword, oldPassword));
    }

    // ✅ 强制定期更换密码
    public boolean isPasswordExpired(User user) {
        return user.getPasswordChangedAt()
            .isBefore(LocalDateTime.now().minusDays(90));
    }
}

2. Token安全

/**
 * Token安全最佳实践
 */
public class TokenSecurityBestPractices {

    // ✅ 短期Access Token + 长期Refresh Token
    private static final long ACCESS_TOKEN_VALIDITY = 15 * 60 * 1000;   // 15分钟
    private static final long REFRESH_TOKEN_VALIDITY = 7 * 24 * 60 * 60 * 1000;  // 7天

    // ✅ Token轮换(每次刷新时生成新的Refresh Token)
    public TokenPair refreshToken(String refreshToken) {
        if (!validateRefreshToken(refreshToken)) {
            throw new InvalidTokenException();
        }

        // 生成新的Token对
        String newAccessToken = generateAccessToken(user);
        String newRefreshToken = generateRefreshToken(user);

        // 废弃旧的Refresh Token
        refreshTokenRepository.deleteByToken(refreshToken);
        refreshTokenRepository.save(newRefreshToken);

        return new TokenPair(newAccessToken, newRefreshToken);
    }

    // ✅ Token黑名单(提前吊销)
    public void revokeToken(String token) {
        Claims claims = parseToken(token);
        long expirationTime = claims.getExpiration().getTime();
        long ttl = expirationTime - System.currentTimeMillis();

        if (ttl > 0) {
            redisTemplate.opsForValue().set(
                "blacklist:" + token,
                "1",
                ttl,
                TimeUnit.MILLISECONDS
            );
        }
    }
}

3. API安全

/**
 * API安全最佳实践
 */
@Configuration
public class ApiSecurityConfig {

    @Bean
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            // ✅ HTTPS Only
            .requiresChannel(channel -> channel
                .anyRequest().requiresSecure()
            )

            // ✅ CORS配置
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))

            // ✅ 安全响应头
            .headers(headers -> headers
                .contentSecurityPolicy("default-src 'self'")
                .frameOptions().deny()
                .xssProtection().enable()
                .contentTypeOptions().enable()
                .httpStrictTransportSecurity()
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000)
            )

            // ✅ 速率限制
            .addFilterBefore(rateLimitFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    // ✅ CORS配置
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://trusted-domain.com"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setExposedHeaders(Arrays.asList("X-Total-Count"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    }
}

4. 数据安全

/**
 * 数据安全最佳实践
 */
public class DataSecurityBestPractices {

    // ✅ 敏感数据加密存储
    @Component
    public class DataEncryptionService {

        private final Cipher cipher;
        private final SecretKey secretKey;

        public String encrypt(String plaintext) {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encrypted = cipher.doFinal(plaintext.getBytes());
            return Base64.getEncoder().encodeToString(encrypted);
        }

        public String decrypt(String ciphertext) {
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] decoded = Base64.getDecoder().decode(ciphertext);
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted);
        }
    }

    // ✅ 数据脱敏
    public String maskSensitiveData(String data, DataType type) {
        switch (type) {
            case PHONE:
                return data.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
            case EMAIL:
                return data.replaceAll("(^\\w{1,3}).*(@.*)", "$1***$2");
            case ID_CARD:
                return data.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
            default:
                return data;
        }
    }

    // ✅ SQL注入防护
    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        // 使用参数化查询
        @Query("SELECT u FROM User u WHERE u.username = :username")
        User findByUsername(@Param("username") String username);

        // ❌ 不要使用字符串拼接
        // @Query("SELECT u FROM User u WHERE u.username = '" + username + "'")
    }
}

5. 日志安全

/**
 * 日志安全最佳实践
 */
@Component
public class SecureLogging {

    private static final Logger logger = LoggerFactory.getLogger(SecureLogging.class);

    // ✅ 不要记录敏感信息
    public void logUserAction(User user, String action) {
        logger.info("User action: user={}, action={}", 
            user.getUsername(),  // ✅ OK
            action);

        // ❌ 不要记录密码、Token等
        // logger.info("Password: {}", user.getPassword());
        // logger.info("Token: {}", token);
    }

    // ✅ 使用日志级别
    public void logSecurityEvent(SecurityEvent event) {
        switch (event.getSeverity()) {
            case CRITICAL:
                logger.error("Security event: {}", event);
                break;
            case WARNING:
                logger.warn("Security event: {}", event);
                break;
            case INFO:
                logger.info("Security event: {}", event);
                break;
        }
    }

    // ✅ 结构化日志
    public void logStructured(String username, String action, boolean success) {
        logger.info("action={}, username={}, success={}, timestamp={}", 
            action, username, success, Instant.now());
    }
}

总结

本章介绍了认证授权的高级主题:

  1. 多租户认证:数据隔离、租户识别、上下文管理
  2. 分布式会话:Redis Session、Session监控、集群管理
  3. 微服务安全:网关认证、服务间调用、分布式追踪
  4. 性能优化:缓存策略、批量处理、数据库优化
  5. 审计监控:日志记录、指标监控、安全告警
  6. 最佳实践:密码、Token、API、数据、日志安全

关键要点: - 安全是系统性工程,需要多层防护 - 性能和安全需要平衡 - 持续监控和审计至关重要 - 遵循最小权限原则 - 定期安全审查和更新

继续学习: - 返回首页:认证授权基础 - OWASP Top 10 - Spring Security官方文档


完整学习路径

  1. 认证授权基础 - 核心概念
  2. 认证协议与标准 - OAuth2、JWT、SSO
  3. Java认证框架对比 - 框架选型
  4. Spring Security核心架构 - 架构原理
  5. Spring Security实战配置 - 实践配置
  6. Spring Security OAuth2集成 - OAuth2实现
  7. 代码示例集 - 完整示例
  8. 高级主题与最佳实践 - 本章

恭喜你完成认证授权知识体系的学习! 🎉