Java Cookie安全案例

wen java案例 3

Java Cookie安全案例详解

不安全的Cookie设置案例

❌ 错误示例1:未设置安全标志

// 不安全的做法 - 生产环境中应避免
Cookie cookie = new Cookie("sessionId", "abc123");
cookie.setMaxAge(3600 * 24 * 30); // 30天过期
response.addCookie(cookie);

问题分析:

Java Cookie安全案例

  • 未设置Secure标志,允许通过HTTP传输
  • 未设置HttpOnly标志,允许JavaScript访问
  • 过期时间过长
  • 未设置SameSite属性

❌ 错误示例2:敏感信息明文存储

// 极不安全 - 切勿使用
Cookie cookie = new Cookie("userInfo", 
    "username=admin;password=123456;role=admin");
cookie.setPath("/");
response.addCookie(cookie);

问题分析:

  • 明文存储敏感信息
  • 任何人都可以读取和篡改
  • 不设置过期时间,成为会话Cookie

安全的Cookie配置案例

✅ 正确示例1:Session ID的安全配置

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
public class SecureCookieUtil {
    public static void setSecureSessionCookie(HttpServletResponse response, 
                                              String sessionId) {
        Cookie sessionCookie = new Cookie("SESSIONID", sessionId);
        // 安全配置
        sessionCookie.setSecure(true);      // 仅HTTPS传输
        sessionCookie.setHttpOnly(true);    // 禁止JavaScript访问
        sessionCookie.setPath("/");         // 设置作用域
        // SameSite属性设置 (Servlet 4.0+)
        // sessionCookie.setAttribute("SameSite", "Strict");
        // 会话Cookie(不设置MaxAge,浏览器关闭即失效)
        // 或者设置合理的过期时间
        sessionCookie.setMaxAge(1800); // 30分钟
        response.addCookie(sessionCookie);
    }
}

✅ 正确示例2:使用加密的Cookie值

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class EncryptedCookieUtil {
    private static final String AES_KEY = "YourSecretKey123"; // 必须16位
    public static String encryptValue(String value) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(
            AES_KEY.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encrypted = cipher.doFinal(value.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encrypted);
    }
    public static String decryptValue(String encryptedValue) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(
            AES_KEY.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decoded = Base64.getDecoder().decode(encryptedValue);
        return new String(cipher.doFinal(decoded), "UTF-8");
    }
    public static void setEncryptedCookie(HttpServletResponse response,
                                          String name, 
                                          String value,
                                          int maxAge) throws Exception {
        String encryptedValue = encryptValue(value);
        Cookie cookie = new Cookie(name, encryptedValue);
        cookie.setSecure(true);
        cookie.setHttpOnly(true);
        cookie.setMaxAge(maxAge);
        cookie.setPath("/");
        response.addCookie(cookie);
    }
}

✅ 正确示例3:Spring Boot安全Cookie配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class SessionCookieConfig {
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        // 安全配置
        serializer.setCookieName("SESSIONID");
        serializer.setCookiePath("/");
        serializer.setUseSecureCookie(true);    // 仅HTTPS
        serializer.setUseHttpOnlyCookie(true);  // 禁止JS访问
        serializer.setCookieMaxAge(1800);       // 30分钟过期
        serializer.setSameSite("Strict");       // SameSite策略
        // 域名限制
        // serializer.setDomainName("example.com");
        return serializer;
    }
}

完整的企业级安全实现

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.UUID;
public class EnterpriseCookieManager {
    private static final int SESSION_TIMEOUT = 1800; // 30分钟
    private static final String COOKIE_NAME = "X-Secure-Token";
    private static final SecureRandom RANDOM = new SecureRandom();
    /**
     * 创建安全的会话Cookie
     */
    public static Cookie createSecureCookie(String userId) {
        // 生成唯一的会话标识
        String sessionToken = generateSessionToken();
        // 创建签名的令牌
        String signedToken = signToken(userId + ":" + sessionToken);
        Cookie cookie = new Cookie(COOKIE_NAME, signedToken);
        cookie.setSecure(true);      // 仅HTTPS
        cookie.setHttpOnly(true);    // 禁止JS访问
        cookie.setPath("/");         // 全站生效
        cookie.setMaxAge(SESSION_TIMEOUT);
        cookie.setDomain(".example.com"); // 设置域名范围
        return cookie;
    }
    /**
     * 验证Cookie的完整性和有效性
     */
    public static boolean validateCookie(Cookie cookie) {
        if (cookie == null) return false;
        try {
            String token = cookie.getValue();
            // 验证签名
            if (!verifySignature(token)) {
                return false;
            }
            // 检查是否过期(从令牌中提取时间戳)
            long timestamp = extractTimestamp(token);
            if (System.currentTimeMillis() - timestamp > SESSION_TIMEOUT * 1000) {
                return false;
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 获取安全的Cookie值
     */
    public static String getSecureValue(HttpServletRequest request, 
                                       String cookieName) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookieName.equals(cookie.getName())) {
                    // 只处理通过HTTPS传输的Cookie
                    if (request.isSecure()) {
                        return cookie.getValue();
                    }
                }
            }
        }
        return null;
    }
    /**
     * 销毁Cookie(登出时使用)
     */
    public static void invalidateCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie(COOKIE_NAME, null);
        cookie.setMaxAge(0);
        cookie.setPath("/");
        cookie.setSecure(true);
        cookie.setHttpOnly(true);
        response.addCookie(cookie);
    }
    private static String generateSessionToken() {
        byte[] randomBytes = new byte[32];
        RANDOM.nextBytes(randomBytes);
        return DatatypeConverter.printHexBinary(randomBytes);
    }
    private static String signToken(String token) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hash = md.digest((token + "secret_salt").getBytes("UTF-8"));
            return token + ":" + DatatypeConverter.printHexBinary(hash);
        } catch (Exception e) {
            throw new RuntimeException("Token signing failed", e);
        }
    }
    private static boolean verifySignature(String signedToken) {
        try {
            String[] parts = signedToken.split(":");
            if (parts.length != 3) return false;
            String originalToken = parts[0] + ":" + parts[1];
            String expectedSignature = parts[2];
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            String computedHash = DatatypeConverter.printHexBinary(
                md.digest((originalToken + "secret_salt").getBytes("UTF-8")));
            return expectedSignature.equals(computedHash);
        } catch (Exception e) {
            return false;
        }
    }
    private static long extractTimestamp(String signedToken) {
        // 假设令牌格式: userId:sessionToken:timestamp:signature
        String[] parts = signedToken.split(":");
        return Long.parseLong(parts[2]);
    }
}

Web安全头配置

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/api/*")
public class SecurityHeadersFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, 
                        ServletResponse response,
                        FilterChain chain) 
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // HSTS(HTTP严格传输安全)
        httpResponse.setHeader("Strict-Transport-Security", 
            "max-age=31536000; includeSubDomains");
        // 防止XSS攻击
        httpResponse.setHeader("X-Content-Type-Options", "nosniff");
        httpResponse.setHeader("X-Frame-Options", "DENY");
        httpResponse.setHeader("X-XSS-Protection", 
            "1; mode=block");
        // 控制资源加载策略
        httpResponse.setHeader("Content-Security-Policy", 
            "default-src 'self'; script-src 'self'");
        // 控制Referer信息
        httpResponse.setHeader("Referrer-Policy", 
            "strict-origin-when-cross-origin");
        // 禁止通过JavaScript访问Cookie
        // 使用Set-Cookie头设置SameSite
        // response.setHeader("Set-Cookie", 
        //     "SESSIONID=xxx; Secure; HttpOnly; SameSite=Strict");
        chain.doFilter(request, response);
    }
}

安全最佳实践总结

安全措施 说明 配置方式
Secure 仅HTTPS传输 cookie.setSecure(true)
HttpOnly 禁止JavaScript访问 cookie.setHttpOnly(true)
SameSite 防止CSRF攻击 SameSite=Strict/Lax
过期时间 限制Cookie生命周期 setMaxAge(1800)
签名验证 防止篡改 HMAC或数字签名
加密 保护敏感数据 AES加密存储
路径限制 限制作用域 setPath("/app")
域名限制 限制访问域名 setDomain(".example.com")

特别注意:

  1. 绝不将密码、信用卡等敏感信息存储在Cookie中
  2. Session ID应该在服务器端进行绑定验证
  3. 定期轮换密钥和会话ID
  4. 对Cookie值进行完整性校验
  5. 使用最新的Servlet规范和浏览器安全特性

案例涵盖了从基础到高级的Java Cookie安全实现,可根据实际需求选择合适的安全级别。

上一篇Java CSP案例

下一篇SameSite案例

抱歉,评论功能暂时关闭!