首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Cookie 中文亂碼終極解決方案:URLEncoder/URLDecoder 正確使用姿勢與最佳實踐

     編輯:Java 綜合教學

問題根因:Cookie 規範不支持非 ASCII 字符

RFC 6265 規定 Cookie 值僅允許可見 ASCII 字符 (排除控制字符、空格、分號、逗號等)。中文屬於非 ASCII 字符,直接寫入會導致:

  • Tomcat/Jetty 等容器拋出 IllegalArgumentException: Control character in cookie value
  • 瀏覽器自動截斷或亂碼存儲
  • 讀取時出現 %E4%B8%AD%E6%96%87 等 URL 編碼殘留

標準解決方案:UTF-8 + URLEncoder/URLDecoder

寫入 Cookie (編碼)

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
    try {
        // 關鍵:UTF-8 編碼,空格轉為 %20 (非 + 號)
        String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name())
                                  .replace("+", "%20"); // 標準 URL 編碼空格為 %20
        Cookie cookie = new Cookie(name, encodedValue);
        cookie.setMaxAge(maxAge);
        cookie.setPath("/"); // 全路徑可見
        cookie.setHttpOnly(true); // 防 XSS
        cookie.setSecure(true);   // 僅 HTTPS 傳輸 (生產環境必開)
        response.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        throw new IllegalStateException("UTF-8 not supported", e); // 不會發生
    }
}

// 使用示例
addCookie(response, "userName", "張三", 3600); // 1小時過期

讀取 Cookie (解碼)

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Optional;

public Optional<String> getCookieValue(HttpServletRequest request, String name) {
    if (request.getCookies() == null) return Optional.empty();
    return Arrays.stream(request.getCookies())
        .filter(c -> name.equals(c.getName()))
        .findFirst()
        .map(cookie -> {
            try {
                return URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8.name());
            } catch (Exception e) {
                log.warn("Cookie decode failed: {}", cookie.getValue(), e);
                return cookie.getValue(); // 降級返回原值
            }
        });
}

// 使用示例
String userName = getCookieValue(request, "userName").orElse("游客"); // 正確顯示「張三」

進階方案:Base64 編碼 (避免 % 字符干擾)

URL 編碼後包含大量 %,某些舊版防火牆/WAF 可能誤攔截。Base64 編碼字符集安全 (A-Z a-z 0-9 + / =) 且無特殊字符:

import java.util.Base64;

public void addCookieBase64(HttpServletResponse response, String name, String value, int maxAge) {
    String encoded = Base64.getUrlEncoder().withoutPadding().encodeToString(value.getBytes(StandardCharsets.UTF_8));
    Cookie cookie = new Cookie(name, encoded);
    cookie.setMaxAge(maxAge);
    cookie.setPath("/");
    cookie.setHttpOnly(true);
    cookie.setSecure(true);
    response.addCookie(cookie);
}

public Optional<String> getCookieValueBase64(HttpServletRequest request, String name) {
    return getCookieValue(request, name).map(v -> {
        try {
            return new String(Base64.getUrlDecoder().decode(v), StandardCharsets.UTF_8);
        } catch (IllegalArgumentException e) {
            log.warn("Base64 decode failed, fallback to URLDecoder", e);
            return URLDecoder.decode(v, StandardCharsets.UTF_8.name()); // 兼容舊 Cookie
        }
    });
}

Spring Boot 整合:封裝為 CookieUtil 組件

@Component
public class CookieUtil {
    private static final String UTF_8 = StandardCharsets.UTF_8.name();
    private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();
    private static final Base64.Decoder DECODER = Base64.getUrlDecoder();

    public void set(HttpServletResponse response, String name, String value, int maxAgeSeconds) {
        String encoded = ENCODER.encodeToString(value.getBytes(UTF_8));
        ResponseCookie cookie = ResponseCookie.from(name, encoded)
            .maxAge(maxAgeSeconds).path("/").httpOnly(true).secure(true).sameSite("Lax").build();
        response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    }

    public Optional<String> get(HttpServletRequest request, String name) {
        Cookie cookie = WebUtils.getCookie(request, name);
        if (cookie == null) return Optional.empty();
        try {
            return Optional.of(new String(DECODER.decode(cookie.getValue()), UTF_8));
        } catch (IllegalArgumentException e) {
            // 兼容舊版 URLEncoder Cookie
            return Optional.of(URLDecoder.decode(cookie.getValue(), UTF_8));
        }
    }

    public void remove(HttpServletResponse response, String name) {
        ResponseCookie cookie = ResponseCookie.from(name, "")
            .maxAge(0).path("/").httpOnly(true).secure(true).build();
        response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    }
}

常見坑點避雷指南

錯誤做法 後果 正確做法
直接存中文 new Cookie("k", "中文")容器拋異常或瀏覽器亂碼必須編碼
用 URLEncoder.encode 但不替換 +空格變 +,解碼後成 + 而非空格.replace("+", "%20") 或用 Base64
編碼用 UTF-8,解碼用 ISO-8859-1亂碼 ()編解碼字符集統一
Cookie 過大 (>4KB)瀏覽器拒絕/截斷大數據存 Session/Redis,Cookie 只存 ID
敏感信息 (Token、ID) 明文存 CookieXSS 竊取、CSRF 攻擊HttpOnly + Secure + SameSite=Lax/Strict

總結

Cookie 中文亂碼本質是字符集不匹配與規範限制。標準解法:寫入時 UTF-8 + URLEncoder (替換 + 號) 或 Base64,讀取時對應解碼。生產環境建議封裝 CookieUtil 統一管理,強制 HttpOnly、Secure、SameSite,兼容新舊編碼格式,徹底消除亂碼與安全隱患。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved