Skip to content

安全最佳实践

1. 认证安全

1.1 密码安全

java
@Configuration
public class PasswordConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }
}

@Service
public class PasswordService {
    
    private final PasswordEncoder passwordEncoder;
    
    public String encodePassword(String rawPassword) {
        return passwordEncoder.encode(rawPassword);
    }
    
    public boolean matches(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
    
    public void validatePasswordStrength(String password) {
        if (password.length() < 8) {
            throw new BusinessException("密码长度至少8位");
        }
        
        if (!password.matches(".*[A-Z].*")) {
            throw new BusinessException("密码必须包含大写字母");
        }
        
        if (!password.matches(".*[a-z].*")) {
            throw new BusinessException("密码必须包含小写字母");
        }
        
        if (!password.matches(".*\\d.*")) {
            throw new BusinessException("密码必须包含数字");
        }
        
        if (!password.matches(".*[!@#$%^&*].*")) {
            throw new BusinessException("密码必须包含特殊字符");
        }
    }
}

1.2 JWT 安全

java
@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private long expiration;
    
    private SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }
    
    public String generateToken(Authentication authentication) {
        User user = (User) authentication.getPrincipal();
        
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);
        
        return Jwts.builder()
                .subject(user.getUsername())
                .claim("userId", user.getId())
                .claim("roles", user.getRoles())
                .issuedAt(now)
                .expiration(expiryDate)
                .signWith(getSigningKey())
                .compact();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            log.warn("Invalid JWT token: {}", e.getMessage());
            return false;
        }
    }
}

1.3 会话管理

java
@Configuration
public class SessionConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .sessionFixation(fixation -> fixation.none())
            )
            .headers(headers -> headers
                .frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
                .xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
                .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
            );
        
        return http.build();
    }
}

2. 授权安全

2.1 细粒度权限控制

java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

@Service
public class OrderService {
    
    @PreAuthorize("hasRole('USER')")
    public Order createOrder(OrderRequest request) {
        return doCreateOrder(request);
    }
    
    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public List<Order> getUserOrders(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    @PreAuthorize("hasPermission(#orderId, 'order', 'read')")
    public Order getOrder(Long orderId) {
        return orderRepository.findById(orderId).orElse(null);
    }
    
    @PostAuthorize("returnObject.userId == authentication.principal.id")
    public Order getOrderById(Long id) {
        return orderRepository.findById(id).orElse(null);
    }
}

2.2 自定义权限评估器

java
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    
    private final OrderRepository orderRepository;
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Object targetId, 
                                Object permission) {
        Long orderId = (Long) targetId;
        String permissionType = (String) permission;
        
        Order order = orderRepository.findById(orderId).orElse(null);
        if (order == null) {
            return false;
        }
        
        User user = (User) authentication.getPrincipal();
        
        if (user.getRoles().contains("ADMIN")) {
            return true;
        }
        
        return order.getUserId().equals(user.getId());
    }
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Serializable targetId, 
                                String targetType, 
                                Object permission) {
        return hasPermission(authentication, targetId, permission);
    }
}

3. 输入验证

3.1 参数验证

java
public record UserRequest(
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度3-20位")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
    String username,
    
    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
             message = "密码至少8位,包含字母、数字和特殊字符")
    String password,
    
    @Email(message = "邮箱格式不正确")
    String email,
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    String phone
) {}

3.2 XSS 防护

java
@Component
public class XssFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        XssHttpServletRequestWrapper wrappedRequest = 
            new XssHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrappedRequest, response);
    }
}

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return sanitize(value);
    }
    
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        
        String[] sanitizedValues = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            sanitizedValues[i] = sanitize(values[i]);
        }
        return sanitizedValues;
    }
    
    private String sanitize(String value) {
        if (value == null) {
            return null;
        }
        
        return value.replaceAll("<", "&lt;")
                   .replaceAll(">", "&gt;")
                   .replaceAll("\"", "&quot;")
                   .replaceAll("'", "&#x27;")
                   .replaceAll("/", "&#x2F;");
    }
}

3.3 SQL 注入防护

java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query("SELECT u FROM User u WHERE u.username = :username")
    Optional<User> findByUsername(@Param("username") String username);
    
    @Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
    Optional<User> findByEmailNative(@Param("email") String email);
}

4. 敏感数据保护

4.1 数据加密

java
@Service
public class EncryptionService {
    
    private final String secretKey;
    
    public String encrypt(String data) {
        try {
            SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            
            byte[] encrypted = cipher.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
    }
    
    public String decrypt(String encryptedData) {
        try {
            SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, key);
            
            byte[] decoded = Base64.getDecoder().decode(encryptedData);
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted);
        } catch (Exception e) {
            throw new RuntimeException("解密失败", e);
        }
    }
}

4.2 日志脱敏

java
@Aspect
@Component
public class SensitiveDataAspect {
    
    @Around("execution(* com.example..*.*(..))")
    public Object maskSensitiveData(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof String str) {
                args[i] = maskIfSensitive(str);
            }
        }
        
        return joinPoint.proceed(args);
    }
    
    private String maskIfSensitive(String value) {
        if (value == null || value.length() < 4) {
            return value;
        }
        
        return value.substring(0, 2) + "****" + value.substring(value.length() - 2);
    }
}

5. 安全配置

5.1 安全响应头

java
@Configuration
public class SecurityHeadersConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'; " +
                                    "script-src 'self' 'unsafe-inline'; " +
                                    "style-src 'self' 'unsafe-inline'; " +
                                    "img-src 'self' data:; " +
                                    "font-src 'self';")
                )
                .frameOptions(frame -> frame.deny())
                .xssProtection(xss -> xss.headerValue(
                    XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000))
            );
        
        return http.build();
    }
}

5.2 CSRF 防护

java
@Configuration
public class CsrfConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/public/**")
            );
        
        return http.build();
    }
}

6. 安全审计

6.1 审计日志

java
@Entity
@Table(name = "audit_logs")
public class AuditLog {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String userId;
    private String action;
    private String resource;
    private String ipAddress;
    private String userAgent;
    private LocalDateTime timestamp;
    private String details;
}

@Aspect
@Component
public class AuditAspect {
    
    private final AuditLogRepository auditLogRepository;
    
    @AfterReturning(pointcut = "@annotation(auditable)", returning = "result")
    public void auditSuccess(JoinPoint joinPoint, Auditable auditable, Object result) {
        saveAuditLog(joinPoint, auditable, null);
    }
    
    @AfterThrowing(pointcut = "@annotation(auditable)", throwing = "exception")
    public void auditFailure(JoinPoint joinPoint, Auditable auditable, Exception exception) {
        saveAuditLog(joinPoint, auditable, exception.getMessage());
    }
    
    private void saveAuditLog(JoinPoint joinPoint, Auditable auditable, String error) {
        HttpServletRequest request = getCurrentRequest();
        
        AuditLog log = new AuditLog();
        log.setUserId(getCurrentUserId());
        log.setAction(auditable.action());
        log.setResource(request.getRequestURI());
        log.setIpAddress(getClientIp(request));
        log.setUserAgent(request.getHeader("User-Agent"));
        log.setTimestamp(LocalDateTime.now());
        log.setDetails(error);
        
        auditLogRepository.save(log);
    }
}

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

7. 小结

本章学习了安全最佳实践:

内容要点
认证安全密码安全、JWT 安全、会话管理
授权安全方法授权、权限评估器
输入验证参数验证、XSS 防护、SQL 注入防护
敏感数据数据加密、日志脱敏
安全配置安全响应头、CSRF 防护
安全审计审计日志、操作追踪

下一章将学习最佳实践总结。