You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

grpc-web架构下Set-Cookie无法在浏览器中生效的问题排查

浏览器无法存储后端返回的JWT Token Cookie问题排查

架构配置

  • 客户端:react-app(grpc-web),地址 192.168.3.2:3000
  • 代理服务器:Envoy,部署在虚拟机,地址 192.168.233.129:8080
  • 后端:java-auth-ms(gRPC),地址 192.168.3.2:8101

客户端调用代码

const grpcPromise = new Promise((resolve, reject) => {
    global.grpcAPI.AccountService.loginUser(
      request,
      { withCredentials: "true" },
      (err, response) => {
        if (!err) {
          resolve({
            status: response.getStatus(),
            msg: response.getMsg(),
          });
        } else {
          reject(err);
        }
      }
    );
  });

Envoy代理配置

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/"
                          route:
                            cluster: grpc_backend
                            timeout: 0s
                            max_stream_duration:
                              grpc_timeout_header_max: 0s
                      cors:
                        allow_origin_string_match:
                          - prefix: "*"
                        allow_methods: GET, PUT, DELETE, POST, OPTIONS
                        allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,withcredentials,set-cookie
                        allow_credentials: true
                        max_age: "1728000"
                        expose_headers: custom-header-1,grpc-status,grpc-message,set-cookie
                http_filters:
                  - name: envoy.filters.http.grpc_web
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
                  - name: envoy.filters.http.cors
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
    - name: grpc_backend
      connect_timeout: 2.25s
      type: logical_dns
      typed_extension_protocol_options:
        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
          "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
          explicit_http_config:
            http2_protocol_options: {}
      lb_policy: round_robin
      load_assignment:
        cluster_name: grpc_backend
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: 192.168.3.2 #host-machine (java-auth-ms)
                      port_value: 8101

Java后端gRPC拦截器代码

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
        ServerCall<ReqT, RespT> call,
        Metadata headers,
        ServerCallHandler<ReqT, RespT> next) {

        ServerCall<ReqT, RespT> wrappedCall = new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
            @Override
            public void sendHeaders(Metadata responseHeaders) {

                Metadata.Key<String> SET_COOKIE_HEADER = Metadata.Key.of("Set-Cookie", Metadata.ASCII_STRING_MARSHALLER);
                String jwtToken = getTokenFromContext(); // Получаем токен из контекста
                String cookie = "token=" + "30ha" + "; HttpOnly; Path=/; Max-Age=" + 24 * 3600; //24

                responseHeaders.put(SET_COOKIE_HEADER, cookie);

                super.sendHeaders(responseHeaders);
            }
        };

        return next.startCall(wrappedCall, headers);
    }


private String getTokenFromContext() {
    return TokenConstants.TOKEN_KEY.get();
}

请求与响应详情

请求头

POST /project_d.grpcAPI.Account.Account/loginUser HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 22
Content-Type: application/grpc-web+proto
Host: 192.168.233.129:8080
Origin: http://192.168.3.2:3000
Pragma: no-cache
Referer: http://192.168.3.2:3000/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
X-Grpc-Web: 1
X-User-Agent: grpc-web-javascript/0.1
withCredentials: true

响应头

HTTP/1.1 200 OK
content-type: application/grpc-web+proto
set-cookie: token=30ha; HttpOnly; Path=/; Max-Age=86400
grpc-encoding: identity
grpc-accept-encoding: gzip
x-envoy-upstream-service-time: 5
access-control-allow-origin: http://192.168.3.2:3000
access-control-allow-credentials: true
access-control-expose-headers: custom-header-1,grpc-status,grpc-message,set-cookie
date: Thu, 19 Sep 2024 13:33:32 GMT
server: envoy
transfer-encoding: chunked

问题描述

响应头中已包含带Token的Set-Cookie字段,但浏览器无法将其存入存储,添加SameSite标识后问题依旧,求原因。


问题原因及解决方案

  1. Cookie Domain属性不匹配
    当前Set-Cookie未指定Domain,浏览器默认将其绑定到请求的Host(192.168.233.129),但客户端运行在192.168.3.2,跨域场景下Domain不匹配,浏览器拒绝存储Cookie。需在Cookie中指定客户端所在IP作为Domain:

    String cookie = "token=30ha; HttpOnly; Path=/; Max-Age=86400; Domain=192.168.3.2";
    
  2. grpc-web请求参数类型错误
    客户端代码中withCredentials传的是字符串"true",而grpc-web要求该参数为布尔值true。虽然部分环境兼容字符串,但标准写法应为布尔值,否则可能导致浏览器不识别该参数,进而忽略Cookie存储:

    { withCredentials: true }
    
  3. SameSite属性需结合环境设置
    跨域场景下,若为HTTP测试环境,建议设置SameSite=Lax(SameSite=Strict会限制跨域请求携带Cookie);若为HTTPS环境,设置SameSite=None; Secure。注意Secure仅在HTTPS下有效,HTTP环境下浏览器会拒绝存储带SameSite=None的Cookie:

    // HTTP环境
    String cookie = "token=30ha; HttpOnly; Path=/; Max-Age=86400; Domain=192.168.3.2; SameSite=Lax";
    // HTTPS环境
    String cookie = "token=30ha; HttpOnly; Path=/; Max-Age=86400; Domain=192.168.3.2; SameSite=None; Secure";
    

内容的提问来源于stack exchange,提问作者L0mchansky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:29:50