常见Web安全漏洞防护

常见Web安全漏洞防护

Web应用面临诸多安全威胁,本文系统讲解XSS、CSRF、SQL注入、API安全等常见漏洞的原理与防御方案,助你构建安全可靠的应用系统。

一、安全态势概览

1.1 OWASP Top 10 (2021)

OWASP Top 10 2021
Broken Access Control
Cryptographic Failures
Injection
Insecure Design
Security Misconfiguration
Vulnerable Components
Auth Failures
Integrity Failures
Logging Failures
SSRF

1.2 防护体系架构

运维监控
基础设施
防护层次
应用层防护
输入验证
输出编码
认证授权
会话管理
网络安全
HTTPS强制
WAF防火墙
API网关
安全监控
日志审计
异常告警
渗透测试
防护效果

二、XSS攻击与防护

2.1 XSS攻击原理

XSS(Cross-Site Scripting)允许攻击者在受害者的浏览器中执行恶意脚本。
受害者浏览器 漏洞服务器 攻击者 受害者浏览器 漏洞服务器 攻击者 浏览器执行恶意脚本 上传恶意脚本到评论区 未过滤,存储恶意内容 访问页面 返回包含恶意脚本的页面 发送Cookie/敏感数据

2.2 XSS三种类型

类型 说明 风险等级
存储型XSS 恶意代码永久存储在服务器 🔴 高
反射型XSS 恶意代码通过URL参数反射 🟡 中
DOM型XSS 前端JS处理不当导致 🟡 中

2.3 Spring Security防护配置

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .securityHeaders(headers -> headers
                // 防止点击劫持
                .frameOptions(frame -> frame.deny())
                
                // XSS 过滤器
                .xssProtection(xss -> xss.enable())
                
                // Content Security Policy
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives(
                        "default-src 'self'; " +
                        "script-src 'self' 'nonce-{nonce}'; " +
                        "style-src 'self' 'unsafe-inline'; " +
                        "img-src 'self' data: https:; " +
                        "font-src 'self'; " +
                        "connect-src 'self'; " +
                        "frame-ancestors 'none';"
                    )))
            
            // 防止 MIME 类型嗅探
            .contentTypeOptions(contentType -> {});
        
        return http.build();
    }
}

2.4 HTML转义工具类

java 复制代码
public class XssDefenseUtils {
    
    private static final Pattern SCRIPT_PATTERN = 
        Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
    private static final Pattern EVENT_PATTERN = 
        Pattern.compile("on\\w+=\"[^\"]*\"", Pattern.CASE_INSENSITIVE);
    private static final Pattern JAVASCRIPT_PATTERN = 
        Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
    
    /**
     * HTML转义,防止XSS
     */
    public static String escapeHtml(String input) {
        if (input == null) {
            return null;
        }
        
        StringBuilder escaped = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            switch (c) {
                case '<':
                    escaped.append("&lt;");
                    break;
                case '>':
                    escaped.append("&gt;");
                    break;
                case '&':
                    escaped.append("&amp;");
                    break;
                case '"':
                    escaped.append("&quot;");
                    break;
                case '\'':
                    escaped.append("&#x27;");
                    break;
                case '/':
                    escaped.append("&#x2F;");
                    break;
                default:
                    escaped.append(c);
            }
        }
        return escaped.toString();
    }
    
    /**
     * 移除危险标签和属性
     */
    public static String stripDangerousTags(String input) {
        if (input == null) {
            return null;
        }
        
        String filtered = input;
        
        // 移除 <script> 标签
        filtered = SCRIPT_PATTERN.matcher(filtered).replaceAll("");
        
        // 移除事件处理器
        filtered = EVENT_PATTERN.matcher(filtered).replaceAll("");
        
        // 移除 javascript: 协议
        filtered = JAVASCRIPT_PATTERN.matcher(filtered).replaceAll("blocked:");
        
        return filtered;
    }
    
    /**
     * URL转义
     */
    public static String escapeUrl(String input) {
        if (input == null) {
            return null;
        }
        try {
            return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            return input;
        }
    }
}

2.5 输入验证与过滤

java 复制代码
@Component
public class XssRequestFilter extends OncePerRequestFilter {
    
    private final XssDefenseUtils xssDefense;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain chain) 
            throws ServletException, IOException {
        
        ContentCachingRequestWrapper wrappedRequest = 
            new ContentCachingRequestWrapper(request);
        
        chain.doFilter(wrappedRequest, response);
    }
    
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null && contentType.contains("multipart/form-data");
    }
}

@Component
public class RequestSanitizer {
    
    /**
     * 清理请求参数中的XSS攻击
     */
    public Map<String, String[]> sanitizeParameters(
            Map<String, String[]> params) {
        
        Map<String, String[]> sanitized = new HashMap<>();
        
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            String key = escapeHtml(entry.getKey());
            String[] values = entry.getValue();
            
            String[] sanitizedValues = new String[values.length];
            for (int i = 0; i < values.length; i++) {
                sanitizedValues[i] = stripDangerousTags(values[i]);
            }
            
            sanitized.put(key, sanitizedValues);
        }
        
        return sanitized;
    }
}

2.6 Thymeleaf XSS防护

html 复制代码
<!-- Thymeleaf 模板会自动转义 -->
<!-- 方式1: 默认转义 -->
<div th:text="${user.comment}"></div>

<!-- 方式2: 显式转义 -->
<div th:utext="${#strings.escapeXml(user.comment)}"></div>

<!-- 方式3: 使用 th:text 而非 th:utext -->

三、CSRF攻击与防护

3.1 CSRF攻击原理

恶意网站 银行网站 受害者 恶意网站 银行网站 受害者 页面自动提交表单 Cookie自动携带 登录网上银行 认证成功,设置Cookie 访问恶意网站 返回钓鱼页面 POST /transfer?to=attacker&amount=10000 执行转账(认为是用户操作) 转账成功

3.2 Spring Security CSRF防护

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. 启用CSRF防护
            .csrf(csrf -> csrf
                // API使用Token头验证
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .csrfRequestAttribute("_csrf")
                
                // 忽略特定端点
                .ignoringRequestMatchers(
                    "/api/public/**",
                    "/webhook/**",
                    "/health/**"
                ))
            
            // 2. 配置Cookie属性
            .addFilterAt(csrfCookieFilter(), BasicAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public CsrfCookieFilter csrfCookieFilter() {
        return new CsrfCookieFilter();
    }
}

public class CsrfCookieFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain chain) 
            throws ServletException, IOException {
        
        // 确保CSRF Cookie被设置
        HttpServletRequest requestWrapper = 
            new HttpServletRequestWrapper(request);
        
        chain.doFilter(requestWrapper, response);
    }
}

3.3 前端CSRF Token处理

javascript 复制代码
// React Hook
function useCsrfToken() {
    const [csrfToken, setCsrfToken] = useState('');
    
    useEffect(() => {
        // 从Cookie获取CSRF Token
        const token = getCookie('XSRF-TOKEN');
        setCsrfToken(token);
    }, []);
    
    return csrfToken;
}

// Axios拦截器
const axiosInstance = axios.create({
    baseURL: '/api'
});

axiosInstance.interceptors.request.use(config => {
    // 从Cookie获取Token
    const csrfToken = getCookie('XSRF-TOKEN');
    
    if (csrfToken) {
        // 添加到请求头
        config.headers['X-XSRF-TOKEN'] = csrfToken;
    }
    
    return config;
});

// Fetch API
async function secureFetch(url, options = {}) {
    const csrfToken = getCookie('XSRF-TOKEN');
    
    return fetch(url, {
        ...options,
        credentials: 'include',  // 包含Cookie
        headers: {
            ...options.headers,
            'X-XSRF-TOKEN': csrfToken
        }
    });
}

3.4 SameSite Cookie防护

java 复制代码
@Configuration
public class CookieConfig {
    
    @Bean
    public SessionCookieConfig sessionCookieConfig() {
        SessionCookieConfig config = new SessionCookieConfig();
        config.setName("SESSIONID");
        config.setHttpOnly(true);
        config.setSecure(true);
        config.setSameSite("Strict");  // 关键防护
        config.setPath("/");
        return config;
    }
    
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("SESSIONID");
        serializer.setCookiePath("/");
        serializer.setUseHttpOnlyCookie(true);
        serializer.setUseSecureCookie(true);
        serializer.setSameSite("Strict");
        return serializer;
    }
}

SameSite 选项说明:

选项 说明 防护效果
Strict 完全阻止跨站Cookie 最安全,但体验差
Lax 导航请求允许 推荐,大多数场景
None 不限制 仅HTTPS + Secure时可用

四、SQL注入与防护

4.1 SQL注入原理

数据库 漏洞应用 攻击者 数据库 漏洞应用 攻击者 正常输入: username = "admin" 攻击输入: username = "admin' OR '1'='1" SELECT * FROM users WHERE username='admin' SELECT * FROM users WHERE username='admin' OR '1'='1' 直接执行攻击SQL 返回所有用户数据 数据泄露

4.2 MyBatis安全使用

xml 复制代码
<!-- 方式1: 使用 #{} 参数化查询(推荐) -->
<select id="findUserByUsername" resultType="User">
    SELECT * FROM users 
    WHERE username = #{username}
    AND status = 1
</select>

<!-- 方式2: 使用 ${} 的危险场景(避免) -->
<!-- 错误示例 -->
<select id="badExample" resultType="User">
    SELECT * FROM ${tableName}  <!-- 危险! -->
    WHERE id = ${id}           <!-- 危险! -->
</select>

<!-- 正确示例:白名单验证 -->
<select id="safeExample" resultType="User">
    SELECT * FROM ${@com.example.SecurityUtils@escapeTableName(tableName)}
    WHERE id = #{id}
</select>
java 复制代码
// 安全的使用方式
@Mapper
public interface UserMapper {
    
    // 正确:使用参数化查询
    User findByUsername(@Param("username") String username);
    
    // 正确:使用IN查询
    List<User> findByIds(@Param("ids") List<Long> ids);
    
    // 正确:动态SQL
    List<User> searchUsers(UserQuery query);
}
xml 复制代码
<!-- MyBatis动态SQL安全写法 -->
<select id="searchUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
    <if test="limit != null">
        LIMIT #{limit}
    </if>
</select>

<!-- IN查询安全写法 -->
<select id="findByIds" resultType="User">
    SELECT * FROM users
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

4.3 JDBC安全写法

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 安全的参数化查询
     */
    public User findByUsername(String username) {
        String sql = "SELECT * FROM users WHERE username = ?";
        return jdbcTemplate.queryForObject(sql, 
            new Object[]{username},
            userRowMapper);
    }
    
    /**
     * 批量插入
     */
    public void batchInsert(List<User> users) {
        String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
        
        List<Object[]> batch = users.stream()
            .map(u -> new Object[]{u.getUsername(), u.getEmail()})
            .collect(Collectors.toList());
        
        jdbcTemplate.batchUpdate(sql, batch);
    }
    
    /**
     * 危险:避免使用字符串拼接
     */
    public User dangerousSearch(String username) {
        // 危险!这会导致SQL注入
        String sql = "SELECT * FROM users WHERE username = '" + username + "'";
        return jdbcTemplate.queryForObject(sql, userRowMapper);
    }
}

4.4 Hibernate安全使用

java 复制代码
@Repository
public class UserRepository {
    
    @Autowired
    private EntityManager entityManager;
    
    /**
     * 使用JPQL参数化查询(安全)
     */
    public User findByUsername(String username) {
        TypedQuery<User> query = entityManager.createQuery(
            "SELECT u FROM User u WHERE u.username = :username AND u.status = 1",
            User.class);
        query.setParameter("username", username);
        return query.getSingleResult();
    }
    
    /**
     * 使用原生SQL(安全)
     */
    public User findByEmail(String email) {
        Query<User> query = entityManager.createNativeQuery(
            "SELECT * FROM users WHERE email = :email", 
            User.class);
        query.setParameter("email", email);
        return query.getSingleResult();
    }
    
    /**
     * 危险:避免字符串拼接
     */
    public User dangerousFind(String field, String value) {
        // 危险!不要这样做
        String jpql = "SELECT u FROM User u WHERE " + field + " = '" + value + "'";
        return entityManager.createQuery(jpql, User.class)
            .getSingleResult();
    }
}

五、敏感数据保护

5.1 敏感数据加密

java 复制代码
@Configuration
public class EncryptionConfig {
    
    @Bean
    public StringEncryptor stringEncryptor() {
        // 使用Jasypt加密
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        encryptor.setPoolSize(2);
        encryptor.setPassword(System.getProperty("encrypt.password"));
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        
        StringIvGenerator ivGenerator = new DefaultIteratingIvGenerator(10);
        encryptor.setIvGenerator(ivGenerator);
        
        return encryptor;
    }
}

@Service
public class SensitiveDataService {
    
    @Autowired
    private StringEncryptor stringEncryptor;
    
    /**
     * 加密敏感字段
     */
    public String encrypt(String plainText) {
        return stringEncryptor.encrypt(plainText);
    }
    
    /**
     * 解密敏感字段
     */
    public String decrypt(String encryptedText) {
        return stringEncryptor.decrypt(encryptedText);
    }
    
    /**
     * 加密身份证号
     */
    public String encryptIdCard(String idCard) {
        return Base64.getEncoder()
            .encodeToString(encrypt(idCard).getBytes());
    }
    
    /**
     * 脱敏身份证号
     */
    public String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() < 10) {
            return idCard;
        }
        return idCard.substring(0, 4) + "****" + 
               idCard.substring(idCard.length() - 4);
    }
}

5.2 配置加密

yaml 复制代码
# application.yml
spring:
  datasource:
    password: ENC(加密后的密码)
    username: ENC(加密后的用户名)
  
  data:
    redis:
      password: ENC(加密后的密码)

# 自定义加密配置
myapp:
  secret-key: ENC(加密后的密钥)
java 复制代码
@Configuration
@PropertySource(value = "classpath:encrypted.properties")
public class EncryptedPropertyConfig {
    
    @Bean
    public static PropertySourcesPlaceholderConfigurer 
            propertySourcesPlaceholderConfigurer() {
        
        PropertySourcesPlaceholderConfigurer configurer = 
            new PropertySourcesPlaceholderConfigurer();
        
        // 配置加密
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        encryptor.setPoolSize(2);
        encryptor.setPassword("my-secret-password");
        encryptor.setAlgorithm("PBEWithMD5AndDES");
        
        EncryptablePropertySourcesPlaceholderResolver resolver = 
            new EncryptablePropertySourcesPlaceholderResolver(encryptor);
        
        configurer.setPlaceholderResolver(resolver);
        
        return configurer;
    }
}

六、API安全防护

6.1 接口签名机制

java 复制代码
@Component
public class ApiSignatureService {
    
    private static final String SECRET_KEY = "your-secret-key";
    
    /**
     * 生成签名
     */
    public String generateSignature(SortedMap<String, String> params) {
        StringBuilder sb = new StringBuilder();
        
        params.forEach((key, value) -> {
            if (value != null && !value.isEmpty()) {
                sb.append(key).append("=").append(value).append("&");
            }
        });
        
        sb.append("key=").append(SECRET_KEY);
        
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }
    
    /**
     * 验证签名
     */
    public boolean verifySignature(SortedMap<String, String> params, 
                                   String signature) {
        String expectedSignature = generateSignature(params);
        return expectedSignature.equalsIgnoreCase(signature);
    }
}

@Component
public class SignatureFilter extends OncePerRequestFilter {
    
    @Autowired
    private ApiSignatureService signatureService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain chain) 
            throws ServletException, IOException {
        
        // 1. 获取签名参数
        String timestamp = request.getHeader("X-Timestamp");
        String nonce = request.getHeader("X-Nonce");
        String signature = request.getHeader("X-Signature");
        
        // 2. 验证时间戳(防重放)
        if (!isValidTimestamp(timestamp)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
                "Invalid timestamp");
            return;
        }
        
        // 3. 验证Nonce(防重放)
        if (!isValidNonce(nonce)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
                "Invalid nonce");
            return;
        }
        
        // 4. 验证签名
        SortedMap<String, String> params = getSortedParams(request);
        if (!signatureService.verifySignature(params, signature)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
                "Invalid signature");
            return;
        }
        
        chain.doFilter(request, response);
    }
    
    private boolean isValidTimestamp(String timestamp) {
        if (timestamp == null) return false;
        try {
            long ts = Long.parseLong(timestamp);
            long now = System.currentTimeMillis() / 1000;
            return Math.abs(now - ts) < 300;  // 5分钟
        } catch (NumberFormatException e) {
            return false;
        }
    }
    
    private boolean isValidNonce(String nonce) {
        // 使用Redis检查Nonce是否已使用
        return !nonceService.isUsed(nonce);
    }
}

6.2 请求限流

java 复制代码
@Component
public class RateLimitFilter extends OncePerRequestFilter {
    
    private final RedisTemplate<String, String> redisTemplate;
    private final RateLimitConfig rateLimitConfig;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain chain) 
            throws ServletException, IOException {
        
        String clientId = getClientId(request);
        String key = "rate_limit:" + clientId;
        
        Long count = redisTemplate.opsForValue().increment(key);
        if (count == 1) {
            redisTemplate.expire(key, 1, TimeUnit.MINUTES);
        }
        
        if (count > rateLimitConfig.getMaxRequestsPerMinute()) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.setContentType("application/json");
            response.getWriter().write(
                "{\"error\": \"Rate limit exceeded\", \"retryAfter\": 60}");
            return;
        }
        
        // 设置限流头
        response.setHeader("X-RateLimit-Remaining", 
            String.valueOf(rateLimitConfig.getMaxRequestsPerMinute() - count));
        
        chain.doFilter(request, response);
    }
    
    private String getClientId(HttpServletRequest request) {
        // 优先使用用户ID
        Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            return auth.getName();
        }
        
        // 否则使用IP
        return request.getRemoteAddr();
    }
}

@Data
@Configuration
public class RateLimitConfig {
    
    private int maxRequestsPerMinute = 60;
    private int maxRequestsPerHour = 1000;
    private int maxRequestsPerDay = 10000;
}

6.3 敏感操作防护

java 复制代码
@Service
public class SensitiveOperationService {
    
    @Autowired
    private SmsService smsService;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 发送验证码
     */
    public void sendVerificationCode(String phone, OperationType type) {
        // 1. 检查发送频率
        String rateKey = "verify:rate:" + phone;
        String count = redisTemplate.opsForValue().get(rateKey);
        
        if (count != null && Integer.parseInt(count) >= 5) {
            throw new RateLimitExceededException("发送过于频繁,请稍后再试");
        }
        
        // 2. 生成验证码
        String code = String.format("%06d", 
            new Random().nextInt(1000000));
        
        // 3. 存储验证码
        String codeKey = "verify:code:" + phone + ":" + type;
        redisTemplate.opsForValue().set(codeKey, code, 5, TimeUnit.MINUTES);
        
        // 4. 更新发送频率
        redisTemplate.opsForValue().increment(rateKey);
        redisTemplate.expire(rateKey, 1, TimeUnit.HOURS);
        
        // 5. 发送短信
        smsService.send(phone, "验证码:" + code + ",5分钟内有效");
    }
    
    /**
     * 验证验证码
     */
    public boolean verifyCode(String phone, String code, OperationType type) {
        String codeKey = "verify:code:" + phone + ":" + type;
        String storedCode = redisTemplate.opsForValue().get(codeKey);
        
        if (storedCode == null) {
            return false;
        }
        
        if (!storedCode.equals(code)) {
            return false;
        }
        
        // 验证成功后删除
        redisTemplate.delete(codeKey);
        return true;
    }
}

public enum OperationType {
    LOGIN,
    REGISTER,
    PASSWORD_RESET,
    PAYMENT,
    BIND_PHONE
}

七、安全响应头

7.1 Spring Security响应头配置

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityHeadersConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) 
            throws Exception {
        
        http
            .securityHeaders(headers -> headers
                // 1. X-Frame-Options:防止点击劫持
                .frameOptions(frame -> frame
                    .deny()  // 完全禁止
                )
                
                // 2. X-Content-Type-Options:防止MIME嗅探
                .contentTypeOptions(contentType -> {})
                
                // 3. X-XSS-Protection:XSS过滤器
                .xssProtection(xss -> xss
                    .enable(true)
                    .block(true)
                )
                
                // 4. Strict-Transport-Security:强制HTTPS
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000)  // 1年
                    . preload(true)
                )
                
                // 5. Content-Security-Policy:内容安全策略
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives(
                        "default-src 'self'; " +
                        "script-src 'self' 'nonce'; " +
                        "style-src 'self' 'unsafe-inline'; " +
                        "img-src 'self' data: https:; " +
                        "font-src 'self'; " +
                        "connect-src 'self' https://api.example.com; " +
                        "frame-ancestors 'none';"
                    )
                )
                
                // 6. Referrer-Policy:来源策略
                .referrerPolicy(referrer -> referrer
                    .policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
                )
                
                // 7. Permissions-Policy:权限策略
                .permissionsPolicy(permissions -> permissions
                    .addPolicyDirective("geolocation=()")
                    .addPolicyDirective("camera=()")
                    .addPolicyDirective("microphone=()")
                    .addPolicyDirective("payment=()")
                )
            );
        
        return http.build();
    }
}

7.2 响应头说明

响应头 目的 推荐值
X-Frame-Options 防止点击劫持 DENY / SAMEORIGIN
X-Content-Type-Options 防止MIME嗅探 nosniff
X-XSS-Protection XSS防护(已被CSP取代) 1; mode=block
Strict-Transport-Security 强制HTTPS max-age=31536000
Content-Security-Policy 内容安全策略 根据业务配置
Referrer-Policy 来源控制 strict-origin-when-cross-origin
Permissions-Policy 功能策略 根据业务配置

7.3 自定义安全头过滤器

java 复制代码
@Component
public class SecurityHeadersFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain chain) 
            throws ServletException, IOException {
        
        // 添加自定义安全头
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
        response.setHeader("X-Download-Options", "noopen");
        response.setHeader("X-Permitted-Cross-Domain-Policies", "none");
        
        // 生成随机数用于CSP
        String nonce = generateNonce();
        request.setAttribute("cspNonce", nonce);
        
        chain.doFilter(request, response);
    }
    
    private String generateNonce() {
        byte[] bytes = new byte[16];
        new SecureRandom().nextBytes(bytes);
        return Base64.getEncoder().encodeToString(bytes);
    }
}

八、输入验证

8.1 Bean Validation配置

java 复制代码
@Configuration
public class ValidationConfig {
    
    @Bean
    public Validator validator() {
        ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
            .configure()
            .failFast(true)  // 快速失败模式
            .addProperty("hibernate.validator.fail_fast", "true")
            .buildValidatorFactory();
        
        return factory.getValidator();
    }
}

@Data
public class UserRegistrationRequest {
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度3-20位")
    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", 
             message = "用户名以字母开头")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, max = 32, message = "密码长度8-32位")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "密码需包含大小写字母和数字")
    private String password;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", 
             message = "手机号格式不正确")
    private String phone;
}

@RestController
public class UserController {
    
    @PostMapping("/register")
    public Result<Void> register(
            @Valid @RequestBody UserRegistrationRequest request,
            BindingResult bindingResult) {
        
        if (bindingResult.hasErrors()) {
            List<String> errors = bindingResult.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.toList());
            return Result.fail(errors);
        }
        
        // 注册逻辑
        return Result.success();
    }
}

8.2 自定义验证器

java 复制代码
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
    String message() default "密码强度不足";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class StrongPasswordValidator 
        implements ConstraintValidator<StrongPassword, String> {
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isEmpty()) {
            return true;  // 使用 @NotBlank 单独验证
        }
        
        // 至少8位
        if (value.length() < 8) {
            return false;
        }
        
        // 包含小写字母
        if (!value.matches(".*[a-z].*")) {
            return false;
        }
        
        // 包含大写字母
        if (!value.matches(".*[A-Z].*")) {
            return false;
        }
        
        // 包含数字
        if (!value.matches(".*\\d.**")) {
            return false;
        }
        
        // 包含特殊字符
        if (!value.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*")) {
            return false;
        }
        
        return true;
    }
}

九、安全日志与监控

9.1 安全事件日志

java 复制代码
@Component
public class SecurityEventLogger {
    
    @Autowired
    private AuditLogService auditLogService;
    
    /**
     * 记录登录事件
     */
    public void logLogin(LoginEvent event) {
        AuditLog log = new AuditLog();
        log.setEventType("LOGIN");
        log.setUsername(event.getUsername());
        log.setIp(event.getIp());
        log.setUserAgent(event.getUserAgent());
        log.setSuccess(event.isSuccess());
        log.setFailReason(event.getFailReason());
        log.setTimestamp(LocalDateTime.now());
        
        auditLogService.save(log);
    }
    
    /**
     * 记录权限检查失败
     */
    public void logAccessDenied(AccessDeniedEvent event) {
        AuditLog log = new AuditLog();
        log.setEventType("ACCESS_DENIED");
        log.setUsername(event.getUsername());
        log.setResource(event.getResource());
        log.setRequiredPermission(event.getRequiredPermission());
        log.setIp(event.getIp());
        log.setTimestamp(LocalDateTime.now());
        
        auditLogService.save(log);
        
        // 告警:连续多次权限拒绝可能表示攻击
        checkAccessDeniedThreshold(event.getUsername());
    }
    
    /**
     * 记录敏感操作
     */
    public void logSensitiveOperation(SensitiveOperationEvent event) {
        AuditLog log = new AuditLog();
        log.setEventType("SENSITIVE_OPERATION");
        log.setUsername(event.getUsername());
        log.setOperation(event.getOperation());
        log.setResource(event.getResource());
        log.setOldValue(event.getOldValue());
        log.setNewValue(event.getNewValue());
        log.setTimestamp(LocalDateTime.now());
        
        auditLogService.save(log);
    }
}

9.2 异常监控告警

java 复制代码
@Component
public class SecurityAlertMonitor {
    
    @Autowired
    private AlertService alertService;
    
    /**
     * 检测暴力破解
     */
    @Scheduled(fixedRate = 60000)  // 每分钟检查
    public void checkBruteForceAttack() {
        // 获取最近5分钟内的失败登录
        List<LoginAttempt> recentFailures = loginAttemptRepository
            .findRecentFailures(5, TimeUnit.MINUTES);
        
        // 按IP分组统计
        Map<String, Long> failuresByIp = recentFailures.stream()
            .collect(Collectors.groupingBy(
                LoginAttempt::getIp, 
                Collectors.counting()));
        
        // 检查是否超过阈值
        for (Map.Entry<String, Long> entry : failuresByIp.entrySet()) {
            if (entry.getValue() > 10) {
                alertService.sendAlert(AlertType.BRUTE_FORCE, 
                    "IP " + entry.getKey() + " 尝试登录失败超过" + 
                    entry.getValue() + "次");
                
                // 自动封禁IP
                ipBlockService.blockIp(entry.getKey(), 30, TimeUnit.MINUTES);
            }
        }
    }
    
    /**
     * 检测异常行为
     */
    @Scheduled(fixedRate = 300000)  // 每5分钟检查
    public void checkAbnormalBehavior() {
        // 检测:同一用户短时间内从多个IP登录
        List<UserLogin> recentLogins = userLoginRepository
            .findRecentLogins(1, TimeUnit.HOUR);
        
        Map<String, Set<String>> ipsByUser = recentLogins.stream()
            .collect(Collectors.groupingBy(
                UserLogin::getUsername,
                Collectors.mapping(UserLogin::getIp, Collectors.toSet())));
        
        for (Map.Entry<String, Set<String>> entry : ipsByUser.entrySet()) {
            if (entry.getValue().size() > 3) {
                alertService.sendAlert(AlertType.ABNORMAL_LOGIN,
                    "用户 " + entry.getKey() + " 在1小时内从" + 
                    entry.getValue().size() + "个不同IP登录");
            }
        }
    }
}

十、渗透测试工具

10.1 常用安全工具

工具 用途 特点
Burp Suite Web渗透测试 功能全面
OWASP ZAP 自动化扫描 开源免费
sqlmap SQL注入检测 自动化SQL注入
Nikto Web服务器扫描 开源
Nmap 端口扫描 网络探测

10.2 自动化安全测试

xml 复制代码
<!-- Maven依赖 -->
<dependency>
    <groupId>org.zaproxy</groupId>
    <artifactId>zap-clientapi</artifactId>
    <version>1.11.0</version>
</dependency>

<!-- Gradle -->
implementation 'org.zaproxy:zap-clientapi:1.11.0'
java 复制代码
@SpringBootTest
class SecurityScanIntegrationTest {
    
    @Test
    void scanForVulnerabilities() {
        // 启动ZAP
        try (ZapClient zap = new ZapClient("http://localhost:8080")) {
            
            // 1. Spider扫描
            zap.spider.scan("http://localhost:8080");
            
            // 2. 主动扫描
            zap.ascan.scan("http://localhost:8080");
            
            // 3. 获取结果
            List<Alert> alerts = zap.getAlerts();
            
            // 4. 断言:无高危漏洞
            long highPriorityCount = alerts.stream()
                .filter(a -> a.getRisk().equals(Risk.HIGH))
                .count();
            
            assertEquals(0, highPriorityCount, 
                "发现 " + highPriorityCount + " 个高危漏洞");
        }
    }
}

十一、总结

11.1 防护检查清单

运维
日志审计
异常告警
定期渗透测试
基础设施
HTTPS
WAF防护
限流防护
输出层
输出编码
安全响应头
CSP策略
处理层
认证授权
会话管理
敏感数据加密
输入层
参数验证
XSS过滤
SQL注入防护

11.2 安全配置检查表

检查项 说明 状态
HTTPS强制 所有HTTP重定向到HTTPS
CSRF防护 启用并配置Token
XSS防护 CSP策略配置
SQL注入 参数化查询
敏感数据加密 数据库字段加密
安全响应头 所有响应添加安全头
输入验证 所有输入参数验证
会话超时 合理配置会话超时
日志审计 记录所有安全事件

安全是持续的过程,而非一次性任务。保持警惕,定期更新依赖库,持续进行安全测试,才能真正保障系统的安全。

相关推荐
听你说325 小时前
库萨星筠:以技术深度定义安全高度,构筑城市3吨级无人环卫车的信任基石
安全
一切皆是因缘际会5 小时前
通用人工智能底层原理:从记忆结构视角解析大模型行为与意识涌现
人工智能·安全·ai·架构·系统架构
@insist1236 小时前
信息安全工程师-防火墙核心技术深度解析:包过滤与状态检测
网络·安全·软考·信息安全工程师·软件水平考试
其实防守也摸鱼6 小时前
CTF密码学综合教学指南--第一章
网络·安全·网络安全·密码学·ctf·法律
一切皆是因缘际会6 小时前
预制式制衡智能:大模型瓶颈下的 AI 迭代新思路
人工智能·安全·ai·架构
Chockmans6 小时前
春秋云境CVE-2015-6522
安全·web安全·网络安全·网络攻击模型·安全威胁分析·春秋云境·cve-2015-6522
聚铭网络6 小时前
【一周安全资讯0425】网安标委技术文件《人工智能应用伦理安全指引》1.0版公开征求意见;Vercel遭第三方OAuth劫持入侵
人工智能·安全
其实防守也摸鱼7 小时前
CTF密码学综合教学指南--第二章
开发语言·网络·python·安全·网络安全·密码学·ctf
X7x57 小时前
传统防火墙:网络安全的第一道防线
安全·网络安全·安全架构·防火墙