前言
大家好,我是老馬。很高興遇到你。
我們?yōu)?java 開發(fā)者實現(xiàn)了 java 版本的 nginx
如果你想知道 servlet 如何處理的,可以參考我的另一個項目:
手寫從零實現(xiàn)簡易版 tomcat minicat
手寫 nginx 系列
如果你對 nginx 原理感興趣,可以閱讀:
從零手寫實現(xiàn) nginx-01-為什么不能有 java 版本的 nginx?
從零手寫實現(xiàn) nginx-02-nginx 的核心能力
從零手寫實現(xiàn) nginx-03-nginx 基于 Netty 實現(xiàn)
從零手寫實現(xiàn) nginx-04-基于 netty http 出入?yún)?yōu)化處理
從零手寫實現(xiàn) nginx-05-MIME類型(Multipurpose Internet Mail Extensions,多用途互聯(lián)網(wǎng)郵件擴展類型)
從零手寫實現(xiàn) nginx-10-sendfile 零拷貝
從零手寫實現(xiàn) nginx-11-file+range 合并
從零手寫實現(xiàn) nginx-12-keep-alive 連接復(fù)用
從零手寫實現(xiàn) nginx-13-nginx.conf 配置文件介紹
從零手寫實現(xiàn) nginx-14-nginx.conf 和 hocon 格式有關(guān)系嗎?
從零手寫實現(xiàn) nginx-15-nginx.conf 如何通過 java 解析處理?
從零手寫實現(xiàn) nginx-16-nginx 支持配置多個 server
從零手寫實現(xiàn) nginx-17-nginx 默認(rèn)配置優(yōu)化
從零手寫實現(xiàn) nginx-18-nginx 請求頭+響應(yīng)頭操作
從零手寫實現(xiàn) nginx-19-nginx cors
從零手寫實現(xiàn) nginx-20-nginx 占位符 placeholder
從零手寫實現(xiàn) nginx-21-nginx modules 模塊信息概覽
從零手寫實現(xiàn) nginx-22-nginx modules 分模塊加載優(yōu)化
從零手寫實現(xiàn) nginx-23-nginx cookie 的操作處理
前言
大家好,我是老馬。
這一節(jié)我們將配置的加載,拆分為不同的模塊加載處理,便于后續(xù)拓展。
1. proxy_set_header Cookie 指令
介紹下 nginx proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie"; 操作 cookie 的指令
在 Nginx 配置文件中,proxy_set_header 指令用于設(shè)置在代理請求中傳遞的 HTTP 頭部字段。
通過 proxy_set_header 可以在將請求轉(zhuǎn)發(fā)給上游服務(wù)器時添加、修改或刪除請求頭部字段。
具體來說,proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie"; 這條指令用于修改請求頭中的 Cookie 字段。
它將一個新的 cookie(admin_cookie=admin_value)添加到現(xiàn)有的請求 cookie 中。詳細(xì)解釋如下:
-
proxy_set_header指令:這是 Nginx 用來設(shè)置請求頭部字段的指令。 -
Cookie:這是要設(shè)置的頭部字段名稱。在這種情況下,設(shè)置的是 HTTP 請求的Cookie頭部。 -
"admin_cookie=admin_value; $http_cookie":這是要設(shè)置的頭部字段值。-
admin_cookie=admin_value:這是要添加的新 cookie 值。admin_cookie是 cookie 的名稱,admin_value是它的值。 -
;:分號用來分隔多個 cookie。 -
$http_cookie:這是一個 Nginx 的內(nèi)置變量,它包含了當(dāng)前請求中的所有 cookie 值。
-
通過這條指令,Nginx 會在轉(zhuǎn)發(fā)請求到上游服務(wù)器之前,將一個新的 cookie 添加到現(xiàn)有的 cookie 中。這樣上游服務(wù)器就會收到一個包含新添加的 admin_cookie=admin_value 的 Cookie 頭部。
示例配置片段如下:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_server;
proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie";
}
}
在這個示例中,當(dāng)客戶端向 example.com 發(fā)起請求時,Nginx 會將請求轉(zhuǎn)發(fā)給 backend_server,并在請求頭部的 Cookie 字段中添加一個新的 admin_cookie=admin_value。
其他相關(guān)的 Nginx 指令
-
proxy_pass:用于定義請求轉(zhuǎn)發(fā)到的上游服務(wù)器。 -
proxy_set_header:用于設(shè)置轉(zhuǎn)發(fā)請求的頭部字段。
注意事項
- 安全性:在操作 cookie 時需要注意安全性,尤其是涉及敏感信息的 cookie。
- 兼容性:確保上游服務(wù)器能夠正確處理添加的 cookie。
-
配置順序:
proxy_set_header通常放在location或server塊中,并在proxy_pass指令之前。
通過合理配置 proxy_set_header 指令,可以在 Nginx 中靈活地操作 HTTP 請求頭部,滿足各種代理需求。
netty 如何實現(xiàn) 對于 cookie 的新增/修改/刪除?
這個我們原來就支持了
/**
* # 增加或修改請求頭
* proxy_set_header X-Real-IP $remote_addr;
* # 刪除請求頭
* proxy_set_header X-Unwanted-Header "";
*
* @param configParam 參數(shù)
* @param context 上下文
*/
@Override
public void doBeforeDispatch(NginxCommonConfigParam configParam, NginxRequestDispatchContext context) {
List<String> values = configParam.getValues();
// $ 占位符號后續(xù)處理
String headerName = values.get(0);
String headerValue = values.get(1);
FullHttpRequest fullHttpRequest = context.getRequest();
// 設(shè)置
HttpHeaders headers = fullHttpRequest.headers();
if (StringUtil.isEmpty(headerValue)) {
headers.remove(headerName);
logger.info(">>>>>>>>>>>> doBeforeDispatch headers.remove({})", headerName);
} else {
// 是否包含
if (headers.contains(headerName)) {
headers.set(headerName, headerValue);
logger.info(">>>>>>>>>>>> doBeforeDispatch headers.set({}, {});", headerName, headerValue);
} else {
headers.add(headerName, headerValue);
logger.info(">>>>>>>>>>>> doBeforeDispatch headers.set({}, {});", headerName, headerValue);
}
}
}
proxy_cookie_domain 指令
解釋
proxy_cookie_domain 是 Nginx 的一個指令,用于修改代理服務(wù)器響應(yīng)中的 Set-Cookie 頭部的 Domain 屬性。
這個指令通常用于在反向代理配置中,當(dāng)上游服務(wù)器設(shè)置的 Domain 屬性與客戶端訪問的域名不一致時,通過重寫 Domain 屬性來解決跨域問題。
語法
proxy_cookie_domain [上游服務(wù)器的域名] [要重寫為的域名];
-
上游服務(wù)器的域名:指定要匹配并重寫的
Domain屬性值。 -
要重寫為的域名:指定新的
Domain屬性值。
默認(rèn)值
proxy_cookie_domain off;
如果不設(shè)置 proxy_cookie_domain,則默認(rèn)不對 Set-Cookie 頭部的 Domain 屬性進(jìn)行任何修改。
配置范圍
該指令可以在 http、server 或 location 塊中配置。
示例
假設(shè)我們有一個后端服務(wù)器 backend.example.com,它在設(shè)置 Cookie 時將 Domain 屬性設(shè)為 backend.example.com。
但是,客戶端訪問的是 www.example.com。
我們可以使用 proxy_cookie_domain 來重寫 Domain 屬性,以便客戶端能夠正確地接收和發(fā)送這些 Cookie。
http {
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://backend.example.com;
proxy_cookie_domain backend.example.com www.example.com;
}
}
}
在這個配置中,當(dāng)上游服務(wù)器 backend.example.com 在響應(yīng)中返回 Set-Cookie 頭部時:
Set-Cookie: sessionid=abcd1234; Domain=backend.example.com; Path=/
Nginx 會將其重寫為:
Set-Cookie: sessionid=abcd1234; Domain=www.example.com; Path=/
使用場景
-
跨域 Cookie 共享:當(dāng)后端服務(wù)器和客戶端使用不同的域名時,通過
proxy_cookie_domain重寫Set-Cookie頭部的Domain屬性,使 Cookie 能夠在客戶端域名下有效。 - 域名變更:如果網(wǎng)站的域名發(fā)生變化,通過該指令可以確保舊域名設(shè)置的 Cookie 仍然有效。
- 子域名問題:在使用子域名時,可以通過該指令將所有子域名的 Cookie 統(tǒng)一到主域名下。
注意事項
- 安全性:確保重寫的域名是可信任的,以防止 Cookie 被不當(dāng)共享。
-
精確匹配:
proxy_cookie_domain的匹配是精確匹配的,因此需要確保指定的上游服務(wù)器域名與實際的Set-Cookie頭部中的Domain屬性完全一致。
通過合理使用 proxy_cookie_domain 指令,可以有效地解決跨域 Cookie 共享的問題,確保在反向代理場景下的 Cookie 設(shè)置和使用正確無誤。
如何通過 netty,實現(xiàn) proxy_cookie_domain 指令特性?
核心實現(xiàn)如下:
/**
* 參數(shù)處理類 響應(yīng)頭處理
*
* @since 0.20.0
* @author 老馬嘯西風(fēng)
*/
public class NginxParamHandleProxyCookieDomain extends AbstractNginxParamLifecycleWrite {
private static final Log logger = LogFactory.getLog(NginxParamHandleProxyCookieDomain.class);
@Override
public void doBeforeWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
if(!(object instanceof HttpResponse)) {
return;
}
List<String> values = configParam.getValues();
if(CollectionUtil.isEmpty(values) || values.size() < 2) {
return;
}
// 原始
String upstreamDomain = values.get(0);
// 目標(biāo)
String targetDomain = values.get(1);
HttpResponse response = (HttpResponse) object;
HttpHeaders headers = response.headers();
String setCookieHeader = headers.get(HttpHeaderNames.SET_COOKIE);
if (setCookieHeader != null) {
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(setCookieHeader);
Set<Cookie> modifiedCookies = cookies.stream().map(cookie -> {
if (upstreamDomain.equals(cookie.domain())) {
Cookie newCookie = new DefaultCookie(cookie.name(), cookie.value());
newCookie.setDomain(targetDomain);
newCookie.setPath(cookie.path());
newCookie.setMaxAge(cookie.maxAge());
newCookie.setSecure(cookie.isSecure());
newCookie.setHttpOnly(cookie.isHttpOnly());
return newCookie;
}
return cookie;
}).collect(Collectors.toSet());
List<String> encodedCookies = ServerCookieEncoder.STRICT.encode(modifiedCookies);
headers.set(HttpHeaderNames.SET_COOKIE, encodedCookies);
}
logger.info(">>>>>>>>>>>> doBeforeWrite proxy_hide_header upstreamDomain={} => targetDomain={}", upstreamDomain, targetDomain);
}
@Override
public void doAfterWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
}
@Override
protected String getKey(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
return "proxy_hide_header";
}
}
proxy_cookie_flags 指令
支持哪些?
在 Nginx 中,proxy_cookie_flags 指令用于設(shè)置從代理服務(wù)器返回給客戶端的 Set-Cookie 頭中特定 cookie 的屬性標(biāo)志。主要支持的配置選項包括:
-
HttpOnly:將
HttpOnly標(biāo)志添加到 cookie,使得 JavaScript 無法通過document.cookie訪問該 cookie。proxy_cookie_flags <cookie_name> HttpOnly; -
Secure:將
Secure標(biāo)志添加到 cookie,僅在通過 HTTPS 協(xié)議發(fā)送時才會發(fā)送該 cookie。proxy_cookie_flags <cookie_name> Secure; -
SameSite:設(shè)置
SameSite標(biāo)志,限制瀏覽器僅在同站點請求時發(fā)送該 cookie,有助于防止跨站點請求偽造(CSRF)攻擊。proxy_cookie_flags <cookie_name> SameSite=Strict;支持的
SameSite值包括Strict、Lax和None。 -
Max-Age:設(shè)置
Max-Age屬性,指定 cookie 的過期時間(秒)。通常用于設(shè)置持久化 cookie 的過期時間。proxy_cookie_flags <cookie_name> Max-Age=3600; -
Expires:設(shè)置
Expires屬性,指定 cookie 的過期時間點。通常以 GMT 格式的日期字符串指定。proxy_cookie_flags <cookie_name> Expires=Wed, 21 Oct 2026 07:28:00 GMT; -
Domain:設(shè)置
Domain屬性,指定可接受該 cookie 的域名范圍。通過proxy_cookie_domain指令更常用地配置。proxy_cookie_flags <cookie_name> Domain=example.com; -
Path:設(shè)置
Path屬性,指定該 cookie 的路徑范圍。proxy_cookie_flags <cookie_name> Path=/;
示例
以下是一些示例,展示如何使用 proxy_cookie_flags 指令設(shè)置不同的 cookie 標(biāo)志:
server {
listen 80;
server_name example.com;
location / {
# 添加 HttpOnly 和 Secure 標(biāo)志
proxy_cookie_flags session_cookie HttpOnly Secure;
# 設(shè)置 SameSite 標(biāo)志為 Strict
proxy_cookie_flags mycookie SameSite=Strict;
# 設(shè)置 Max-Age 為 1 小時
proxy_cookie_flags persistent_cookie Max-Age=3600;
# 設(shè)置 Expires 屬性
proxy_cookie_flags old_cookie Expires=Wed, 21 Oct 2026 07:28:00 GMT;
# 設(shè)置 Domain 屬性
proxy_cookie_flags global_cookie Domain=example.com;
# 設(shè)置 Path 屬性
proxy_cookie_flags local_cookie Path=/subpath;
proxy_pass http://backend;
}
}
通過這些配置,您可以靈活地控制從代理服務(wù)器返回的 Set-Cookie 頭中各個 cookie 的屬性,以滿足安全需求和業(yè)務(wù)邏輯。
java 核心實現(xiàn)
public void doBeforeWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
if(!(object instanceof HttpResponse)) {
return;
}
List<String> values = configParam.getValues();
if(CollectionUtil.isEmpty(values) || values.size() < 2) {
return;
}
HttpResponse response = (HttpResponse) object;
HttpHeaders headers = response.headers();
String cookieHeader = headers.get(HttpHeaderNames.COOKIE);
final String cookieName = values.get(0);
if (cookieHeader != null) {
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieHeader);
Set<Cookie> modifiedCookies = cookies.stream().map(cookie -> {
// 相同的名字
if (cookieName.equals(cookie.name())) {
// HttpOnly Secure
for(int i = 1; i < values.size(); i++) {
String value = values.get(i);
if("HttpOnly".equals(value)) {
cookie.setHttpOnly(true);
}
if("Secure".equals(value)) {
cookie.setSecure(true);
}
// 拆分
if(!value.contains("=")) {
return cookie;
}
String[] items = value.split("=");
String itemKey = items[0];
String itemVal = items[1];
// if("SameSite".equals(itemKey) && "Strict".equals(itemVal)) {
// }
if("Max-Age".equals(itemKey)) {
cookie.setMaxAge(Long.parseLong(itemVal));
}
if("Expires".equals(itemKey)) {
Date expireDate = calcDate(itemVal);
long maxAge = expireDate.getTime() - System.currentTimeMillis();
cookie.setMaxAge(maxAge);
}
if("Domain".equals(itemKey)) {
cookie.setDomain(itemVal);
}
if("Path".equals(itemKey)) {
cookie.setPath(itemVal);
}
}
}
return cookie;
}).collect(Collectors.toSet());
List<String> encodedCookies = ServerCookieEncoder.STRICT.encode(modifiedCookies);
headers.set(HttpHeaderNames.COOKIE, encodedCookies);
}
logger.info(">>>>>>>>>>>> doBeforeWrite proxy_cookie_flags values={}", values);
}
nginx proxy_cookie_path 指令
介紹
在 Nginx 中,proxy_cookie_path 指令用于修改傳遞到后端服務(wù)器的 HTTP 請求中的 Cookie 的路徑。
這個指令通常在反向代理服務(wù)器配置中使用,用于調(diào)整傳遞給后端服務(wù)器的 Cookie 的路徑信息,以適應(yīng)后端服務(wù)器的預(yù)期路徑結(jié)構(gòu)。
語法和用法
語法:
proxy_cookie_path regex path;
參數(shù)解釋:
-
regex:一個正則表達(dá)式,用于匹配要修改的 Cookie 的路徑。 -
path:要替換成的路徑。
示例
假設(shè)有如下配置:
location /app/ {
proxy_pass http://backend.example.com;
proxy_cookie_path ~*^/app(.*) $1;
}
在這個示例中:
-
proxy_cookie_path指令配合proxy_pass使用,表示將從客戶端接收的帶有路徑/app/的 Cookie 的路徑信息去除/app部分后再傳遞給后端服務(wù)器。
例如,如果客戶端發(fā)送的 Cookie 路徑是 /app/session, Nginx 將修改為 /session 后傳遞給后端服務(wù)器。
注意事項
- 使用
proxy_cookie_path時,確保理解你的后端服務(wù)器期望接收的 Cookie 路徑格式,以便正確設(shè)置正則表達(dá)式和路徑。 - 正則表達(dá)式必須能夠正確匹配客戶端發(fā)送的 Cookie 路徑。
- 這個指令通常用于調(diào)整不同路徑的代理請求,以便與后端服務(wù)器的預(yù)期路徑結(jié)構(gòu)匹配。
java 核心實現(xiàn)
public void doBeforeDispatch(NginxCommonConfigParam configParam, NginxRequestDispatchContext context) {
List<String> values = configParam.getValues();
if(CollectionUtil.isEmpty(values) || values.size() < 2) {
throw new Nginx4jException("proxy_cookie_path 必須包含2個參數(shù)");
}
FullHttpRequest request = context.getRequest();
// 原始
String regex = values.get(0);
String path = values.get(1);
HttpHeaders headers = request.headers();
String cookieHeader = headers.get(HttpHeaderNames.COOKIE);
if (cookieHeader != null) {
String modifiedCookieHeader = cookieHeader.replaceAll(regex, path);
headers.set(HttpHeaderNames.COOKIE, modifiedCookieHeader);
}
logger.info(">>>>>>>>>>>> doBeforeDispatch proxy_cookie_path replace regex={} => path={}", regex, path);
}
小結(jié)
對于 cookie 的處理,讓我們的請求可以更加強大靈活。
proxy_cookie_domain: 設(shè)置后端服務(wù)器響應(yīng)的 Cookie 中的域名。proxy_cookie_flags: 設(shè)置后端服務(wù)器響應(yīng)的 Cookie 的標(biāo)志位。proxy_cookie_path: 設(shè)置后端服務(wù)器響應(yīng)的 Cookie 的路徑。
我是老馬,期待與你的下次重逢。
開源地址
為了便于大家學(xué)習(xí),已經(jīng)將 nginx 開源