安全最佳实践
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("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """)
.replaceAll("'", "'")
.replaceAll("/", "/");
}
}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 防护 |
| 安全审计 | 审计日志、操作追踪 |
下一章将学习最佳实践总结。