security-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Audit Skill

安全审计技能

Security checklist for Java applications based on OWASP Top 10 and secure coding practices.
基于OWASP Top 10和安全编码实践的Java应用安全检查清单。

When to Use

适用场景

  • Security code review
  • Before production releases
  • User asks about "security", "vulnerability", "OWASP"
  • Reviewing authentication/authorization code
  • Checking for injection vulnerabilities

  • 安全代码审查
  • 生产版本发布前
  • 用户询问「安全」「漏洞」「OWASP」相关问题时
  • 审查身份认证/授权代码
  • 检查注入类漏洞

OWASP Top 10 Quick Reference

OWASP Top 10 快速参考

#RiskJava Mitigation
A01Broken Access ControlRole-based checks, deny by default
A02Cryptographic FailuresUse strong algorithms, no hardcoded secrets
A03InjectionParameterized queries, input validation
A04Insecure DesignThreat modeling, secure defaults
A05Security MisconfigurationDisable debug, secure headers
A06Vulnerable ComponentsDependency scanning, updates
A07Authentication FailuresStrong passwords, MFA, session management
A08Data Integrity FailuresVerify signatures, secure deserialization
A09Logging FailuresLog security events, no sensitive data
A10SSRFValidate URLs, allowlist domains

序号风险Java防护方案
A01访问控制失效基于角色的校验,默认拒绝策略
A02加密机制失效使用强加密算法,禁止硬编码密钥
A03注入漏洞参数化查询,输入验证
A04不安全设计威胁建模,安全默认配置
A05安全配置错误关闭调试模式,配置安全响应头
A06存在漏洞的依赖组件依赖扫描,及时更新版本
A07身份认证失效强密码要求,多因素认证,会话管理
A08数据完整性失效签名校验,安全反序列化
A09日志与监控失效记录安全事件,禁止输出敏感数据
A10服务端请求伪造(SSRF)校验URL,域名白名单

Input Validation (All Frameworks)

输入验证(全框架适用)

Bean Validation (JSR 380)

Bean Validation (JSR 380)

Works in Spring, Quarkus, Jakarta EE, and standalone.
java
// ✅ GOOD: Validate at boundary
public class CreateUserRequest {

    @NotNull(message = "Username is required")
    @Size(min = 3, max = 50, message = "Username must be 3-50 characters")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, underscore")
    private String username;

    @NotNull
    @Email(message = "Invalid email format")
    private String email;

    @NotNull
    @Size(min = 8, max = 100)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "Password must contain uppercase, lowercase, and number")
    private String password;

    @Min(value = 0, message = "Age cannot be negative")
    @Max(value = 150, message = "Invalid age")
    private Integer age;
}

// Controller/Resource - trigger validation
public Response createUser(@Valid CreateUserRequest request) {
    // request is already validated
}
适用于Spring、Quarkus、Jakarta EE以及独立Java应用。
java
// ✅ GOOD: Validate at boundary
public class CreateUserRequest {

    @NotNull(message = "Username is required")
    @Size(min = 3, max = 50, message = "Username must be 3-50 characters")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, underscore")
    private String username;

    @NotNull
    @Email(message = "Invalid email format")
    private String email;

    @NotNull
    @Size(min = 8, max = 100)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "Password must contain uppercase, lowercase, and number")
    private String password;

    @Min(value = 0, message = "Age cannot be negative")
    @Max(value = 150, message = "Invalid age")
    private Integer age;
}

// Controller/Resource - trigger validation
public Response createUser(@Valid CreateUserRequest request) {
    // request is already validated
}

Custom Validators

自定义校验器

java
// Custom annotation
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SafeHtmlValidator.class)
public @interface SafeHtml {
    String message() default "Contains unsafe HTML";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator implementation
public class SafeHtmlValidator implements ConstraintValidator<SafeHtml, String> {

    private static final Pattern DANGEROUS_PATTERN = Pattern.compile(
        "<script|javascript:|on\\w+\\s*=", Pattern.CASE_INSENSITIVE
    );

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true;
        return !DANGEROUS_PATTERN.matcher(value).find();
    }
}
java
// Custom annotation
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SafeHtmlValidator.class)
public @interface SafeHtml {
    String message() default "Contains unsafe HTML";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator implementation
public class SafeHtmlValidator implements ConstraintValidator<SafeHtml, String> {

    private static final Pattern DANGEROUS_PATTERN = Pattern.compile(
        "<script|javascript:|on\\w+\\s*=", Pattern.CASE_INSENSITIVE
    );

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true;
        return !DANGEROUS_PATTERN.matcher(value).find();
    }
}

Allowlist vs Blocklist

白名单 vs 黑名单

java
// ❌ BAD: Blocklist (attackers find bypasses)
if (input.contains("<script>")) {
    throw new ValidationException("Invalid input");
}

// ✅ GOOD: Allowlist (only permit known-good)
private static final Pattern SAFE_NAME = Pattern.compile("^[a-zA-Z\\s'-]{1,100}$");

if (!SAFE_NAME.matcher(input).matches()) {
    throw new ValidationException("Invalid name format");
}

java
// ❌ BAD: Blocklist (attackers find bypasses)
if (input.contains("<script>")) {
    throw new ValidationException("Invalid input");
}

// ✅ GOOD: Allowlist (only permit known-good)
private static final Pattern SAFE_NAME = Pattern.compile("^[a-zA-Z\\s'-]{1,100}$");

if (!SAFE_NAME.matcher(input).matches()) {
    throw new ValidationException("Invalid name format");
}

SQL Injection Prevention

SQL注入防护

JPA/Hibernate (All Frameworks)

JPA/Hibernate (全框架适用)

java
// ✅ GOOD: Parameterized queries
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// ✅ GOOD: Criteria API
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("email"), email));  // Safe

// ✅ GOOD: Named parameters
TypedQuery<User> query = entityManager.createQuery(
    "SELECT u FROM User u WHERE u.status = :status", User.class);
query.setParameter("status", status);  // Safe

// ❌ BAD: String concatenation
String jpql = "SELECT u FROM User u WHERE u.email = '" + email + "'";  // VULNERABLE!
java
// ✅ GOOD: Parameterized queries
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// ✅ GOOD: Criteria API
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("email"), email));  // Safe

// ✅ GOOD: Named parameters
TypedQuery<User> query = entityManager.createQuery(
    "SELECT u FROM User u WHERE u.status = :status", User.class);
query.setParameter("status", status);  // Safe

// ❌ BAD: String concatenation
String jpql = "SELECT u FROM User u WHERE u.email = '" + email + "'";  // VULNERABLE!

Native Queries

原生查询

java
// ✅ GOOD: Parameterized native query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);

// ❌ BAD: Concatenated native query
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!
java
// ✅ GOOD: Parameterized native query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);

// ❌ BAD: Concatenated native query
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!

JDBC (Plain Java)

JDBC (原生Java)

java
// ✅ GOOD: PreparedStatement
String sql = "SELECT * FROM users WHERE email = ? AND status = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
    stmt.setString(1, email);
    stmt.setString(2, status);
    ResultSet rs = stmt.executeQuery();
}

// ❌ BAD: Statement with concatenation
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!
Statement stmt = connection.createStatement();
stmt.executeQuery(sql);

java
// ✅ GOOD: PreparedStatement
String sql = "SELECT * FROM users WHERE email = ? AND status = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
    stmt.setString(1, email);
    stmt.setString(2, status);
    ResultSet rs = stmt.executeQuery();
}

// ❌ BAD: Statement with concatenation
String sql = "SELECT * FROM users WHERE email = '" + email + "'";  // VULNERABLE!
Statement stmt = connection.createStatement();
stmt.executeQuery(sql);

XSS Prevention

XSS防护

Output Encoding

输出编码

java
// ✅ GOOD: Use templating engine's auto-escaping

// Thymeleaf - auto-escapes by default
<p th:text="${userInput}">...</p>  // Safe

// To display HTML (dangerous, use carefully):
<p th:utext="${trustedHtml}">...</p>  // Only for trusted content!

// ✅ GOOD: Manual encoding when needed
import org.owasp.encoder.Encode;

String safe = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
String safeUrl = Encode.forUriComponent(userInput);
Maven dependency for OWASP Encoder:
xml
<dependency>
    <groupId>org.owasp.encoder</groupId>
    <artifactId>encoder</artifactId>
    <version>1.2.3</version>
</dependency>
java
// ✅ GOOD: Use templating engine's auto-escaping

// Thymeleaf - auto-escapes by default
<p th:text="${userInput}">...</p>  // Safe

// To display HTML (dangerous, use carefully):
<p th:utext="${trustedHtml}">...</p>  // Only for trusted content!

// ✅ GOOD: Manual encoding when needed
import org.owasp.encoder.Encode;

String safe = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
String safeUrl = Encode.forUriComponent(userInput);
OWASP Encoder的Maven依赖:
xml
<dependency>
    <groupId>org.owasp.encoder</groupId>
    <artifactId>encoder</artifactId>
    <version>1.2.3</version>
</dependency>

Content Security Policy

内容安全策略(CSP)

java
// Add CSP header to prevent inline scripts

// Spring Boot
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; script-src 'self'; style-src 'self'")
            )
        );
        return http.build();
    }
}

// Servlet Filter (works everywhere)
@WebFilter("/*")
public class SecurityHeadersFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Content-Security-Policy", "default-src 'self'");
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        chain.doFilter(req, res);
    }
}

java
// 添加CSP响应头禁止内联脚本

// Spring Boot
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; script-src 'self'; style-src 'self'")
            )
        );
        return http.build();
    }
}

// Servlet Filter (全场景适用)
@WebFilter("/*")
public class SecurityHeadersFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Content-Security-Policy", "default-src 'self'");
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        chain.doFilter(req, res);
    }
}

CSRF Protection

CSRF防护

Spring Security

Spring Security

java
// CSRF enabled by default for browser clients
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // For REST APIs with JWT (stateless) - can disable CSRF
            .csrf(csrf -> csrf.disable())

            // For browser apps with sessions - keep CSRF enabled
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}
java
// CSRF默认对浏览器客户端开启
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 对于使用JWT的无状态REST API可以关闭CSRF
            .csrf(csrf -> csrf.disable())

            // 对于带会话的浏览器应用保持CSRF开启
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            );
        return http.build();
    }
}

Quarkus

Quarkus

properties
undefined
properties
undefined

application.properties

application.properties

quarkus.http.csrf.enabled=true quarkus.http.csrf.cookie-name=XSRF-TOKEN

---
quarkus.http.csrf.enabled=true quarkus.http.csrf.cookie-name=XSRF-TOKEN

---

Authentication & Authorization

身份认证与授权

Password Storage

密码存储

java
// ✅ GOOD: Use BCrypt or Argon2
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

// BCrypt (widely supported)
PasswordEncoder encoder = new BCryptPasswordEncoder(12);  // strength 12
String hash = encoder.encode(rawPassword);
boolean matches = encoder.matches(rawPassword, hash);

// Argon2 (recommended for new projects)
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(rawPassword);

// ❌ BAD: MD5, SHA1, SHA256 without salt
String hash = DigestUtils.md5Hex(password);  // NEVER for passwords!
java
// ✅ GOOD: 使用BCrypt或Argon2
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

// BCrypt (兼容性广)
PasswordEncoder encoder = new BCryptPasswordEncoder(12);  // strength 12
String hash = encoder.encode(rawPassword);
boolean matches = encoder.matches(rawPassword, hash);

// Argon2 (新项目推荐使用)
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(rawPassword);

// ❌ BAD: 未加盐的MD5、SHA1、SHA256
String hash = DigestUtils.md5Hex(password);  // NEVER for passwords!

Authorization Checks

授权校验

java
// ✅ GOOD: Check authorization at service layer
@Service
public class DocumentService {

    public Document getDocument(Long documentId, User currentUser) {
        Document doc = documentRepository.findById(documentId)
            .orElseThrow(() -> new NotFoundException("Document not found"));

        // Authorization check
        if (!doc.getOwnerId().equals(currentUser.getId()) &&
            !currentUser.hasRole("ADMIN")) {
            throw new AccessDeniedException("Not authorized to access this document");
        }

        return doc;
    }
}

// ❌ BAD: Only check at controller level, trust user input
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
    return documentRepository.findById(id).orElseThrow();  // No auth check!
}
java
// ✅ GOOD: 在服务层做授权校验
@Service
public class DocumentService {

    public Document getDocument(Long documentId, User currentUser) {
        Document doc = documentRepository.findById(documentId)
            .orElseThrow(() -> new NotFoundException("Document not found"));

        // 授权校验
        if (!doc.getOwnerId().equals(currentUser.getId()) &&
            !currentUser.hasRole("ADMIN")) {
            throw new AccessDeniedException("Not authorized to access this document");
        }

        return doc;
    }
}

// ❌ BAD: 仅在控制层校验,信任用户输入
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
    return documentRepository.findById(id).orElseThrow();  // No auth check!
}

Spring Security Annotations

Spring Security注解

java
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() { }

@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public void ownDataOnly(Long userId) { }

@PreAuthorize("@authService.canAccess(#documentId, authentication)")
public Document getDocument(Long documentId) { }

java
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() { }

@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public void ownDataOnly(Long userId) { }

@PreAuthorize("@authService.canAccess(#documentId, authentication)")
public Document getDocument(Long documentId) { }

Secrets Management

密钥管理

Never Hardcode Secrets

禁止硬编码密钥

java
// ❌ BAD: Hardcoded secrets
private static final String API_KEY = "sk-1234567890abcdef";
private static final String DB_PASSWORD = "admin123";

// ✅ GOOD: Environment variables
String apiKey = System.getenv("API_KEY");

// ✅ GOOD: External configuration
@Value("${api.key}")
private String apiKey;

// ✅ GOOD: Secrets manager
@Autowired
private SecretsManager secretsManager;
String apiKey = secretsManager.getSecret("api-key");
java
// ❌ BAD: 硬编码密钥
private static final String API_KEY = "sk-1234567890abcdef";
private static final String DB_PASSWORD = "admin123";

// ✅ GOOD: 从环境变量读取
String apiKey = System.getenv("API_KEY");

// ✅ GOOD: 外部配置文件读取
@Value("${api.key}")
private String apiKey;

// ✅ GOOD: 密钥管理服务读取
@Autowired
private SecretsManager secretsManager;
String apiKey = secretsManager.getSecret("api-key");

Configuration Files

配置文件

yaml
undefined
yaml
undefined

✅ GOOD: Reference environment variables

✅ GOOD: 引用环境变量

spring: datasource: password: ${DB_PASSWORD}
api: key: ${API_KEY}
spring: datasource: password: ${DB_PASSWORD}
api: key: ${API_KEY}

❌ BAD: Hardcoded in application.yml

❌ BAD: 硬编码在application.yml中

spring: datasource: password: admin123 # NEVER!
undefined
spring: datasource: password: admin123 # NEVER!
undefined

.gitignore

.gitignore配置

gitignore
undefined
gitignore
undefined

Never commit these

禁止提交以下文件

.env *.pem *.key credentials secret application-local.yml

---
.env *.pem *.key credentials secret application-local.yml

---

Secure Deserialization

安全反序列化

Avoid Java Serialization

避免使用Java原生序列化

java
// ❌ DANGEROUS: Java ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject();  // Remote Code Execution risk!

// ✅ GOOD: Use JSON with Jackson
ObjectMapper mapper = new ObjectMapper();
// Disable dangerous features
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL
);  // Be careful with polymorphic types!

User user = mapper.readValue(json, User.class);
java
// ❌ DANGEROUS: Java ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject();  // Remote Code Execution risk!

// ✅ GOOD: 使用Jackson做JSON序列化
ObjectMapper mapper = new ObjectMapper();
// 禁用危险特性
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL
);  // 多态类型使用需谨慎!

User user = mapper.readValue(json, User.class);

Jackson Security

Jackson安全配置

java
// ✅ Configure Jackson safely
@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // Prevent unknown properties exploitation
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // Don't allow class type in JSON (prevents gadget attacks)
        mapper.deactivateDefaultTyping();

        return mapper;
    }
}

java
// ✅ 安全配置Jackson
@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // 防止未知属性利用
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 禁止JSON中指定类类型(防止Gadget攻击)
        mapper.deactivateDefaultTyping();

        return mapper;
    }
}

Dependency Security

依赖安全

OWASP Dependency Check

OWASP依赖检查

Maven:
xml
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>9.0.7</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>  <!-- Fail on high severity -->
    </configuration>
</plugin>
Run:
bash
mvn dependency-check:check
Maven配置:
xml
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>9.0.7</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>  <!-- 高危漏洞时阻断构建 -->
    </configuration>
</plugin>
运行命令:
bash
mvn dependency-check:check

Report: target/dependency-check-report.html

报告路径: target/dependency-check-report.html

undefined
undefined

Keep Dependencies Updated

保持依赖更新

bash
undefined
bash
undefined

Check for updates

检查可更新的依赖

mvn versions:display-dependency-updates
mvn versions:display-dependency-updates

Update to latest

更新到最新版本

mvn versions:use-latest-releases

---
mvn versions:use-latest-releases

---

Security Headers

安全响应头

Recommended Headers

推荐配置

HeaderValuePurpose
Content-Security-Policy
default-src 'self'
Prevent XSS
X-Content-Type-Options
nosniff
Prevent MIME sniffing
X-Frame-Options
DENY
Prevent clickjacking
Strict-Transport-Security
max-age=31536000
Force HTTPS
X-XSS-Protection
1; mode=block
Legacy XSS filter
响应头取值作用
Content-Security-Policy
default-src 'self'
防范XSS
X-Content-Type-Options
nosniff
禁止MIME类型嗅探
X-Frame-Options
DENY
防范点击劫持
Strict-Transport-Security
max-age=31536000
强制使用HTTPS
X-XSS-Protection
1; mode=block
兼容旧浏览器的XSS过滤器

Spring Boot Configuration

Spring Boot配置

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers(headers -> headers
        .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
        .frameOptions(frame -> frame.deny())
        .httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000))
        .contentTypeOptions(Customizer.withDefaults())
    );
    return http.build();
}

java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers(headers -> headers
        .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
        .frameOptions(frame -> frame.deny())
        .httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000))
        .contentTypeOptions(Customizer.withDefaults())
    );
    return http.build();
}

Logging Security Events

安全事件日志

java
// ✅ Log security-relevant events
log.info("User login successful", kv("userId", userId), kv("ip", clientIp));
log.warn("Failed login attempt", kv("username", username), kv("ip", clientIp), kv("attempt", attemptCount));
log.warn("Access denied", kv("userId", userId), kv("resource", resourceId), kv("action", action));
log.error("Authentication failure", kv("reason", reason), kv("ip", clientIp));

// ❌ NEVER log sensitive data
log.info("Login: user={}, password={}", username, password);  // NEVER!
log.debug("Request body: {}", requestWithCreditCard);  // NEVER!

java
// ✅ 记录安全相关事件
log.info("User login successful", kv("userId", userId), kv("ip", clientIp));
log.warn("Failed login attempt", kv("username", username), kv("ip", clientIp), kv("attempt", attemptCount));
log.warn("Access denied", kv("userId", userId), kv("resource", resourceId), kv("action", action));
log.error("Authentication failure", kv("reason", reason), kv("ip", clientIp));

// ❌ 禁止记录敏感数据
log.info("Login: user={}, password={}", username, password);  // NEVER!
log.debug("Request body: {}", requestWithCreditCard);  // NEVER!

Security Checklist

安全检查清单

Code Review

代码审查

  • Input validated with allowlist patterns
  • SQL queries use parameters (no concatenation)
  • Output encoded for context (HTML, JS, URL)
  • Authorization checked at service layer
  • No hardcoded secrets
  • Passwords hashed with BCrypt/Argon2
  • Sensitive data not logged
  • CSRF protection enabled (for browser apps)
  • 输入使用白名单规则验证
  • SQL查询使用参数化(无字符串拼接)
  • 输出按场景编码(HTML、JS、URL)
  • 服务层做授权校验
  • 无硬编码密钥
  • 密码使用BCrypt/Argon2哈希存储
  • 无敏感数据日志输出
  • 浏览器应用开启CSRF防护

Configuration

配置检查

  • HTTPS enforced
  • Security headers configured
  • Debug/dev features disabled in production
  • Default credentials changed
  • Error messages don't leak internal details
  • 强制使用HTTPS
  • 配置安全响应头
  • 生产环境关闭调试/开发特性
  • 修改默认凭证
  • 错误信息不泄露内部细节

Dependencies

依赖检查

  • No known vulnerabilities (OWASP check)
  • Dependencies up to date
  • Unnecessary dependencies removed

  • 无已知漏洞(通过OWASP检查)
  • 依赖版本为最新
  • 移除未使用的依赖

Related Skills

相关技能

  • java-code-review
    - General code review
  • maven-dependency-audit
    - Dependency vulnerability scanning
  • logging-patterns
    - Secure logging practices
  • java-code-review
    - 通用代码审查
  • maven-dependency-audit
    - 依赖漏洞扫描
  • logging-patterns
    - 安全日志实践