security-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSecurity 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 快速参考
| # | Risk | Java Mitigation |
|---|---|---|
| A01 | Broken Access Control | Role-based checks, deny by default |
| A02 | Cryptographic Failures | Use strong algorithms, no hardcoded secrets |
| A03 | Injection | Parameterized queries, input validation |
| A04 | Insecure Design | Threat modeling, secure defaults |
| A05 | Security Misconfiguration | Disable debug, secure headers |
| A06 | Vulnerable Components | Dependency scanning, updates |
| A07 | Authentication Failures | Strong passwords, MFA, session management |
| A08 | Data Integrity Failures | Verify signatures, secure deserialization |
| A09 | Logging Failures | Log security events, no sensitive data |
| A10 | SSRF | Validate 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
undefinedproperties
undefinedapplication.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
undefinedyaml
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!
undefinedspring:
datasource:
password: admin123 # NEVER!
undefined.gitignore
.gitignore配置
gitignore
undefinedgitignore
undefinedNever 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:checkMaven配置:
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:checkReport: target/dependency-check-report.html
报告路径: target/dependency-check-report.html
undefinedundefinedKeep Dependencies Updated
保持依赖更新
bash
undefinedbash
undefinedCheck 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
推荐配置
| Header | Value | Purpose |
|---|---|---|
| | Prevent XSS |
| | Prevent MIME sniffing |
| | Prevent clickjacking |
| | Force HTTPS |
| | Legacy XSS filter |
| 响应头 | 取值 | 作用 |
|---|---|---|
| | 防范XSS |
| | 禁止MIME类型嗅探 |
| | 防范点击劫持 |
| | 强制使用HTTPS |
| | 兼容旧浏览器的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
相关技能
- - General code review
java-code-review - - Dependency vulnerability scanning
maven-dependency-audit - - Secure logging practices
logging-patterns
- - 通用代码审查
java-code-review - - 依赖漏洞扫描
maven-dependency-audit - - 安全日志实践
logging-patterns