从零手写实现 nginx-23-nginx 对于 cookie 的操作

打印 上一主题 下一主题

主题 926|帖子 926|积分 2782

前言

各人好,我是老马。很高兴碰到你。
我们为 java 开发者实现了 java 版本的 nginx
https://github.com/houbb/nginx4j
假如你想知道 servlet 如何处理的,可以参考我的另一个项目:
手写从零实现简易版 tomcat minicat
手写 nginx 系列

假如你对 nginx 原理感兴趣,可以阅读:
从零手写实现 nginx-01-为什么不能有 java 版本的 nginx?
从零手写实现 nginx-02-nginx 的焦点能力
从零手写实现 nginx-03-nginx 基于 Netty 实现
从零手写实现 nginx-04-基于 netty http 出入参优化处理
从零手写实现 nginx-05-MIME类型(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型)
从零手写实现 nginx-06-文件夹自动索引
从零手写实现 nginx-07-大文件下载
从零手写实现 nginx-08-范围查询
从零手写实现 nginx-09-文件压缩
从零手写实现 nginx-10-sendfile 零拷贝
从零手写实现 nginx-11-file+range 合并
从零手写实现 nginx-12-keep-alive 连接复用
从零手写实现 nginx-13-nginx.conf 配置文件介绍
从零手写实现 nginx-14-nginx.conf 和 hocon 格式有关系吗?
从零手写实现 nginx-15-nginx.conf 如何通过 java 解析处理?
从零手写实现 nginx-16-nginx 支持配置多个 server
从零手写实现 nginx-17-nginx 默认配置优化
从零手写实现 nginx-18-nginx 请求头+响应头操作
从零手写实现 nginx-19-nginx cors
从零手写实现 nginx-20-nginx 占位符 placeholder
从零手写实现 nginx-21-nginx modules 模块信息概览
从零手写实现 nginx-22-nginx modules 分模块加载优化
从零手写实现 nginx-23-nginx cookie 的操作处理
前言

各人好,我是老马。
这一节我们将配置的加载,拆分为不同的模块加载处理,便于后续拓展。
1. proxy_set_header Cookie 指令

介绍下 nginx proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie"; 操作 cookie 的指令

在 Nginx 配置文件中,proxy_set_header 指令用于设置在代理请求中传递的 HTTP 头部字段。
通过 proxy_set_header 可以在将请求转发给上游服务器时添加、修改或删除请求头部字段。
具体来说,proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie"; 这条指令用于修改请求头中的 Cookie 字段。
它将一个新的 cookie(admin_cookie=admin_value)添加到现有的请求 cookie 中。具体表明如下:

  • proxy_set_header 指令:这是 Nginx 用来设置请求头部字段的指令。
  • Cookie:这是要设置的头部字段名称。在这种情况下,设置的是 HTTP 请求的 Cookie 头部。
  • "admin_cookie=admin_value; $http_cookie":这是要设置的头部字段值。

    • admin_cookie=admin_value:这是要添加的新 cookie 值。admin_cookie 是 cookie 的名称,admin_value 是它的值。
    • ;:分号用来分隔多个 cookie。
    • $http_cookie:这是一个 Nginx 的内置变量,它包罗了当前请求中的所有 cookie 值。

通过这条指令,Nginx 会在转发请求到上游服务器之前,将一个新的 cookie 添加到现有的 cookie 中。这样上游服务器就会收到一个包罗新添加的 admin_cookie=admin_value 的 Cookie 头部。
示例配置片段如下:
  1. server {
  2.     listen 80;
  3.     server_name example.com;
  4.     location / {
  5.         proxy_pass http://backend_server;
  6.         proxy_set_header Cookie "admin_cookie=admin_value; $http_cookie";
  7.     }
  8. }
复制代码
在这个示例中,当客户端向 example.com 发起请求时,Nginx 会将请求转发给 backend_server,并在请求头部的 Cookie 字段中添加一个新的 admin_cookie=admin_value。
其他相干的 Nginx 指令


  • proxy_pass:用于定义请求转发到的上游服务器。
  • proxy_set_header:用于设置转发请求的头部字段。
注意事项


  • 安全性:在操作 cookie 时需要注意安全性,尤其是涉及敏感信息的 cookie。
  • 兼容性:确保上游服务器可以大概正确处理添加的 cookie。
  • 配置次序:proxy_set_header 通常放在 location 或 server 块中,并在 proxy_pass 指令之前。
通过合理配置 proxy_set_header 指令,可以在 Nginx 中机动地操作 HTTP 请求头部,满足各种代理需求。
netty 如何实现 对于 cookie 的新增/修改/删除?

这个我们原来就支持了
  1.     /**
  2.      * # 增加或修改请求头
  3.      * proxy_set_header X-Real-IP $remote_addr;
  4.      * # 删除请求头
  5.      * proxy_set_header X-Unwanted-Header "";
  6.      *
  7.      * @param configParam 参数
  8.      * @param context     上下文
  9.      */
  10.     @Override
  11.     public void doBeforeDispatch(NginxCommonConfigParam configParam, NginxRequestDispatchContext context) {
  12.         List<String> values = configParam.getValues();
  13.         // $ 占位符号后续处理
  14.         String headerName = values.get(0);
  15.         String headerValue = values.get(1);
  16.         FullHttpRequest fullHttpRequest = context.getRequest();
  17.         // 设置
  18.         HttpHeaders headers = fullHttpRequest.headers();
  19.         if (StringUtil.isEmpty(headerValue)) {
  20.             headers.remove(headerName);
  21.             logger.info(">>>>>>>>>>>> doBeforeDispatch headers.remove({})", headerName);
  22.         } else {
  23.             // 是否包含
  24.             if (headers.contains(headerName)) {
  25.                 headers.set(headerName, headerValue);
  26.                 logger.info(">>>>>>>>>>>> doBeforeDispatch headers.set({}, {});", headerName, headerValue);
  27.             } else {
  28.                 headers.add(headerName, headerValue);
  29.                 logger.info(">>>>>>>>>>>> doBeforeDispatch headers.set({}, {});", headerName, headerValue);
  30.             }
  31.         }
  32.     }
复制代码
proxy_cookie_domain 指令

表明

proxy_cookie_domain 是 Nginx 的一个指令,用于修改代理服务器响应中的 Set-Cookie 头部的 Domain 属性。
这个指令通常用于在反向代理配置中,当上游服务器设置的 Domain 属性与客户端访问的域名不同等时,通过重写 Domain 属性来办理跨域问题。
语法
  1. proxy_cookie_domain [上游服务器的域名] [要重写为的域名];
复制代码

  • 上游服务器的域名:指定要匹配并重写的 Domain 属性值。
  • 要重写为的域名:指定新的 Domain 属性值。
默认值
  1. proxy_cookie_domain off;
复制代码
假如不设置 proxy_cookie_domain,则默认不对 Set-Cookie 头部的 Domain 属性进行任何修改。
配置范围

该指令可以在 http、server 或 location 块中配置。
示例

假设我们有一个后端服务器 backend.example.com,它在设置 Cookie 时将 Domain 属性设为 backend.example.com。
但是,客户端访问的是 www.example.com。
我们可以利用 proxy_cookie_domain 来重写 Domain 属性,以便客户端可以大概正确地接收和发送这些 Cookie。
  1. http {
  2.     server {
  3.         listen 80;
  4.         server_name www.example.com;
  5.         location / {
  6.             proxy_pass http://backend.example.com;
  7.             proxy_cookie_domain backend.example.com www.example.com;
  8.         }
  9.     }
  10. }
复制代码
在这个配置中,当上游服务器 backend.example.com 在响应中返回 Set-Cookie 头部时:
  1. Set-Cookie: sessionid=abcd1234; Domain=backend.example.com; Path=/
复制代码
Nginx 会将其重写为:
  1. Set-Cookie: sessionid=abcd1234; Domain=www.example.com; Path=/
复制代码
利用场景


  • 跨域 Cookie 共享:当后端服务器和客户端利用不同的域名时,通过 proxy_cookie_domain 重写 Set-Cookie 头部的 Domain 属性,使 Cookie 可以大概在客户端域名下有效。
  • 域名变更:假如网站的域名发生变化,通过该指令可以确保旧域名设置的 Cookie 仍然有效。
  • 子域名问题:在利用子域名时,可以通过该指令将所有子域名的 Cookie 同一到主域名下。
注意事项


  • 安全性:确保重写的域名是可信任的,以防止 Cookie 被不当共享。
  • 精确匹配:proxy_cookie_domain 的匹配是精确匹配的,因此需要确保指定的上游服务器域名与实际的 Set-Cookie 头部中的 Domain 属性完全同等。
通过合理利用 proxy_cookie_domain 指令,可以有效地办理跨域 Cookie 共享的问题,确保在反向代理场景下的 Cookie 设置和利用正确无误。
如何通过 netty,实现 proxy_cookie_domain 指令特性?

焦点实现如下:
  1. /**
  2. * 参数处理类 响应头处理
  3. *
  4. * @since 0.20.0
  5. * @author 老马啸西风
  6. */
  7. public class NginxParamHandleProxyCookieDomain extends AbstractNginxParamLifecycleWrite {
  8.     private static final Log logger = LogFactory.getLog(NginxParamHandleProxyCookieDomain.class);
  9.     @Override
  10.     public void doBeforeWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
  11.         if(!(object instanceof HttpResponse)) {
  12.             return;
  13.         }
  14.         List<String> values = configParam.getValues();
  15.         if(CollectionUtil.isEmpty(values) || values.size() < 2) {
  16.             return;
  17.         }
  18.         // 原始
  19.         String upstreamDomain = values.get(0);
  20.         // 目标
  21.         String targetDomain = values.get(1);
  22.         HttpResponse response = (HttpResponse) object;
  23.         HttpHeaders headers = response.headers();
  24.         String setCookieHeader = headers.get(HttpHeaderNames.SET_COOKIE);
  25.         if (setCookieHeader != null) {
  26.             Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(setCookieHeader);
  27.             Set<Cookie> modifiedCookies = cookies.stream().map(cookie -> {
  28.                 if (upstreamDomain.equals(cookie.domain())) {
  29.                     Cookie newCookie = new DefaultCookie(cookie.name(), cookie.value());
  30.                     newCookie.setDomain(targetDomain);
  31.                     newCookie.setPath(cookie.path());
  32.                     newCookie.setMaxAge(cookie.maxAge());
  33.                     newCookie.setSecure(cookie.isSecure());
  34.                     newCookie.setHttpOnly(cookie.isHttpOnly());
  35.                     return newCookie;
  36.                 }
  37.                 return cookie;
  38.             }).collect(Collectors.toSet());
  39.             List<String> encodedCookies = ServerCookieEncoder.STRICT.encode(modifiedCookies);
  40.             headers.set(HttpHeaderNames.SET_COOKIE, encodedCookies);
  41.         }
  42.         logger.info(">>>>>>>>>>>> doBeforeWrite proxy_hide_header upstreamDomain={} => targetDomain={}", upstreamDomain, targetDomain);
  43.     }
  44.     @Override
  45.     public void doAfterWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
  46.     }
  47.     @Override
  48.     protected String getKey(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
  49.         return "proxy_hide_header";
  50.     }
  51. }
复制代码
proxy_cookie_flags 指令

支持哪些?

在 Nginx 中,proxy_cookie_flags 指令用于设置从代理服务器返回给客户端的 Set-Cookie 头中特定 cookie 的属性标志。主要支持的配置选项包罗:

  • HttpOnly:将 HttpOnly 标志添加到 cookie,使得 JavaScript 无法通过 document.cookie 访问该 cookie。
    1. proxy_cookie_flags <cookie_name> HttpOnly;
    复制代码
  • Secure:将 Secure 标志添加到 cookie,仅在通过 HTTPS 协议发送时才会发送该 cookie。
    1. proxy_cookie_flags <cookie_name> Secure;
    复制代码
  • SameSite:设置 SameSite 标志,限定浏览器仅在同站点请求时发送该 cookie,有助于防止跨站点请求伪造(CSRF)攻击。
    1. proxy_cookie_flags <cookie_name> SameSite=Strict;
    复制代码
    支持的 SameSite 值包罗 Strict、Lax 和 None。
  • Max-Age:设置 Max-Age 属性,指定 cookie 的过期时间(秒)。通常用于设置持久化 cookie 的过期时间。
    1. proxy_cookie_flags <cookie_name> Max-Age=3600;
    复制代码
  • Expires:设置 Expires 属性,指定 cookie 的过期时间点。通常以 GMT 格式的日期字符串指定。
    1. proxy_cookie_flags <cookie_name> Expires=Wed, 21 Oct 2026 07:28:00 GMT;
    复制代码
  • Domain:设置 Domain 属性,指定可接受该 cookie 的域名范围。通过 proxy_cookie_domain 指令更常用地配置。
    1. proxy_cookie_flags <cookie_name> Domain=example.com;
    复制代码
  • Path:设置 Path 属性,指定该 cookie 的路径范围。
    1. proxy_cookie_flags <cookie_name> Path=/;
    复制代码
示例

以下是一些示例,展示如何利用 proxy_cookie_flags 指令设置不同的 cookie 标志:
  1. server {
  2.     listen 80;
  3.     server_name example.com;
  4.     location / {
  5.         # 添加 HttpOnly 和 Secure 标志
  6.         proxy_cookie_flags session_cookie HttpOnly Secure;
  7.         
  8.         # 设置 SameSite 标志为 Strict
  9.         proxy_cookie_flags mycookie SameSite=Strict;
  10.         
  11.         # 设置 Max-Age 为 1 小时
  12.         proxy_cookie_flags persistent_cookie Max-Age=3600;
  13.         
  14.         # 设置 Expires 属性
  15.         proxy_cookie_flags old_cookie Expires=Wed, 21 Oct 2026 07:28:00 GMT;
  16.         
  17.         # 设置 Domain 属性
  18.         proxy_cookie_flags global_cookie Domain=example.com;
  19.         
  20.         # 设置 Path 属性
  21.         proxy_cookie_flags local_cookie Path=/subpath;
  22.         
  23.         proxy_pass http://backend;
  24.     }
  25. }
复制代码
通过这些配置,您可以机动地控制从代理服务器返回的 Set-Cookie 头中各个 cookie 的属性,以满足安全需求和业务逻辑。
java 焦点实现
  1.     public void doBeforeWrite(NginxCommonConfigParam configParam, ChannelHandlerContext ctx, Object object, NginxRequestDispatchContext context) {
  2.         if(!(object instanceof HttpResponse)) {
  3.             return;
  4.         }
  5.         List<String> values = configParam.getValues();
  6.         if(CollectionUtil.isEmpty(values) || values.size() < 2) {
  7.             return;
  8.         }
  9.         HttpResponse response = (HttpResponse) object;
  10.         HttpHeaders headers = response.headers();
  11.         String cookieHeader = headers.get(HttpHeaderNames.COOKIE);
  12.         final String cookieName = values.get(0);
  13.         if (cookieHeader != null) {
  14.             Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieHeader);
  15.             Set<Cookie> modifiedCookies = cookies.stream().map(cookie -> {
  16.                 // 相同的名字
  17.                 if (cookieName.equals(cookie.name())) {
  18.                     // HttpOnly Secure
  19.                     for(int i = 1; i < values.size(); i++) {
  20.                         String value = values.get(i);
  21.                         if("HttpOnly".equals(value)) {
  22.                             cookie.setHttpOnly(true);
  23.                         }
  24.                         if("Secure".equals(value)) {
  25.                             cookie.setSecure(true);
  26.                         }
  27.                         // 拆分
  28.                         if(!value.contains("=")) {
  29.                             return cookie;
  30.                         }
  31.                         String[] items = value.split("=");
  32.                         String itemKey = items[0];
  33.                         String itemVal = items[1];
  34. //                        if("SameSite".equals(itemKey) && "Strict".equals(itemVal)) {
  35. //                        }
  36.                         if("Max-Age".equals(itemKey)) {
  37.                             cookie.setMaxAge(Long.parseLong(itemVal));
  38.                         }
  39.                         if("Expires".equals(itemKey)) {
  40.                             Date expireDate = calcDate(itemVal);
  41.                             long maxAge = expireDate.getTime() - System.currentTimeMillis();
  42.                             cookie.setMaxAge(maxAge);
  43.                         }
  44.                         if("Domain".equals(itemKey)) {
  45.                             cookie.setDomain(itemVal);
  46.                         }
  47.                         if("Path".equals(itemKey)) {
  48.                             cookie.setPath(itemVal);
  49.                         }
  50.                     }
  51.                 }
  52.                 return cookie;
  53.             }).collect(Collectors.toSet());
  54.             List<String> encodedCookies = ServerCookieEncoder.STRICT.encode(modifiedCookies);
  55.             headers.set(HttpHeaderNames.COOKIE, encodedCookies);
  56.         }
  57.         logger.info(">>>>>>>>>>>> doBeforeWrite proxy_cookie_flags values={}", values);
  58.     }
复制代码
nginx proxy_cookie_path 指令

介绍

在 Nginx 中,proxy_cookie_path 指令用于修改传递到后端服务器的 HTTP 请求中的 Cookie 的路径。
这个指令通常在反向代理服务器配置中利用,用于调整传递给后端服务器的 Cookie 的路径信息,以适应后端服务器的预期路径结构。
语法和用法

语法:
  1. proxy_cookie_path regex path;
复制代码
参数表明:

  • regex:一个正则表达式,用于匹配要修改的 Cookie 的路径。
  • path:要替换成的路径。
示例

假设有如下配置:
  1. location /app/ {
  2.     proxy_pass http://backend.example.com;
  3.     proxy_cookie_path ~*^/app(.*) $1;
  4. }
复制代码
在这个示例中:

  • proxy_cookie_path 指令共同 proxy_pass 利用,表示将从客户端接收的带有路径 /app/ 的 Cookie 的路径信息去除 /app 部分后再传递给后端服务器。
例如,假如客户端发送的 Cookie 路径是 /app/session, Nginx 将修改为 /session 后传递给后端服务器。
注意事项


  • 利用 proxy_cookie_path 时,确保明白你的后端服务器期望接收的 Cookie 路径格式,以便正确设置正则表达式和路径。
  • 正则表达式必须可以大概正确匹配客户端发送的 Cookie 路径。
  • 这个指令通常用于调整不同路径的代理请求,以便与后端服务器的预期路径结构匹配。
java 焦点实现
  1. public void doBeforeDispatch(NginxCommonConfigParam configParam, NginxRequestDispatchContext context) {
  2.     List<String> values = configParam.getValues();
  3.     if(CollectionUtil.isEmpty(values) || values.size() < 2) {
  4.         throw new Nginx4jException("proxy_cookie_path 必须包含2个参数");
  5.     }
  6.     FullHttpRequest request = context.getRequest();
  7.     // 原始
  8.     String regex = values.get(0);
  9.     String path = values.get(1);
  10.     HttpHeaders headers = request.headers();
  11.     String cookieHeader = headers.get(HttpHeaderNames.COOKIE);
  12.     if (cookieHeader != null) {
  13.         String modifiedCookieHeader = cookieHeader.replaceAll(regex, path);
  14.         headers.set(HttpHeaderNames.COOKIE, modifiedCookieHeader);
  15.     }
  16.     logger.info(">>>>>>>>>>>> doBeforeDispatch proxy_cookie_path replace regex={} => path={}", regex, path);
  17. }
复制代码
小结

对于 cookie 的处理,让我们的请求可以更加强大机动。

  • proxy_cookie_domain: 设置后端服务器响应的 Cookie 中的域名。
  • proxy_cookie_flags: 设置后端服务器响应的 Cookie 的标志位。
  • proxy_cookie_path: 设置后端服务器响应的 Cookie 的路径。
我是老马,期待与你的下次重逢。
开源地址

为了便于各人学习,已经将 nginx 开源
https://github.com/houbb/nginx4j

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表