【问题标题】:SameSite cookie in Java applicationJava 应用程序中的 SameSite cookie
【发布时间】:2017-07-31 17:29:39
【问题描述】:

您知道任何允许为 cookie 设置自定义标志的 Java cookie 实现,例如 SameSite=strict?似乎javax.servlet.http.Cookie 有一组严格限制的可以添加的标志。

【问题讨论】:

标签: java cookies csrf flags


【解决方案1】:

我不是 JEE 专家,但我认为由于 cookie 属性是一项新发明,因此您不能期望它会出现在 Java EE 7 接口或实现中。 Cookie 类似乎缺少通用属性的设置器。但不是通过

将 cookie 添加到您的HttpServletResponse
response.addCookie(myCookie)

您可以通过

简单地设置相应的HTTP头字段
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")

更新:感谢 @mwyrzyk 指出 setHeader() 会覆盖所有现有的同名标头。因此,如果您的响应中已经有其他 Set-Cookie 标头,那么您当然可以使用具有相同参数的 addHeader()

【讨论】:

  • 为什么投反对票?根据 StackOverflow 的规定,投反对票是针对质量非常低的草率答案,显示出严重缺乏研究工作或知识。在撰写本文时,到目前为止,我是唯一一个与 OP 交谈并寻找解决方法的人,因为他想要的目前是不可能的。我什至解释了为什么它不能按他希望的方式工作。即使您不喜欢我的建议,也不应投反对票。相反,您可以提供更好的解决方案。
  • 请记住,使用setHeader() 方法将删除所有以前的同名标头,因此我必须执行this 之类的操作
  • 您的解决方案对 jsp 页面有效吗?我将您的 sn-p 放入 <% %> 但我收到此错误:Cookie “JSESSIONID” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute
  • @HoseinAqajani,请提出一个新问题,以可重复的方式描述您的情况,然后从那里链接到此答案。我从来没有使用过 JSP,也无法回答你的问题。
  • @PanadolChong,这是关于服务器端和 Java 的,而不是关于特定浏览器的。我从来没有遇到过任何问题。再说了,现在的 Chrome 版本是 90,你为什么用 50 测试呢? Here你可以看到这个功能是在 Chrome 51 中引入的,所以在那之前它不起作用也就不足为奇了。
【解决方案2】:

如果您不想更新所有代码,您也可以使用 Apache 或 Nginx 配置(或您正在使用的任何其他 HTTP 服务器/代理)通过一行配置来实现相同的目的

1 使用 Apache 配置设置 SameSite cookie

您可以将以下行添加到您的 Apache 配置中

Header always edit Set-Cookie (.*) "$1; SameSite=Lax"

这将使用SameSite=Lax 标志更新您的所有cookie

在此处查看更多信息:https://blog.giantgeek.com/?p=1872

2 使用 Nginx 配置设置 SameSite cookie

location / {
    # your usual config ...
    # hack, set all cookies to secure, httponly and samesite (strict or lax)
    proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
}

这里也一样,这也会用SameSite=Lax 标志更新你的所有cookie

在此处查看更多信息:https://serverfault.com/questions/849888/add-samesite-to-cookies-using-nginx-as-reverse-proxy

【讨论】:

  • 知道什么可能会阻止方法 1 在 ProxyPass 情况下工作吗?该行确实编辑 Set-Cookie 插入的标头,例如Header always add Set-Cookie "foo-bar" 在 apache 配置中,但不修改代理响应中的标头。
【解决方案3】:

截至今天 (24.01.20) servlet-api 不允许将 sameSite 属性设置为 cookie。顺便说一句,有一个正在进行的票 (LINK) 将发布一个新的(5.0 或 5.1 servlet-api)。

方案一:你不急,可以等servlet-api版本,其中Cookie类和SessionCookieConfig类有专门的方法来设置sameSite属性。

选项 2: 您使用的是旧版本的 servlet-api(例如 3.1),因此是旧版本的 Tomcat(例如,我现在有当前的情况)。这意味着即使社区发布 servlet-api 并支持 sameSite,您也无法立即更新您的版本,因为更新几个主要版本的风险太大。
在这种情况下,我们找到了解决方案。
Tomcat中有一个Cookie Processor ComponentLINK,它

CookieProcessor 元素表示将接收到的 cookie 标头解析为可通过 HttpServletRequest.getCookies() 访问的 javax.servlet.http.Cookie 对象并转换通过 HttpServletResponse.addCookie() 添加到响应的 javax.servlet.http.Cookie 对象的组件到返回给客户端的 HTTP 标头。

该处理器的使用非常简单。 context.xml 内部:

<Context>
    ...
    <CookieProcessor sameSiteCookies="none"/>
</Context>

在这种情况下,使用处理器的默认实现 (org.apache.tomcat.util.http.Rfc6265CookieProcessor),但您可以在 CookieProcessor 属性 className 中指定任何其他实现。

【讨论】:

    【解决方案4】:

    如果您有现有的代码,那么毫无疑问您已经使用了 java servlet Cookie 对象。我们当然有,所以我们想要破坏性最小的选择。 @kriegaex 的答案简洁明了,但基本上是对 cookie 进行硬编码,并且不重用 cookie 对象。为了扩展他的答案,我们编写了这个函数来处理相同的站点功能,同时维护现有的 Cookie 对象功能。此答案旨在用于您需要在响应对象上添加多个 cookie 的情况下,而无需更改可能已经在标头上的现有 cookie。当然,另一种选择是编写一个新的 cookie 类并扩展功能,但这需要对现有代码进行比我们在这里提出的更多的更改。

    请注意,使用此解决方案,只需更改一行现有代码(每个 cookie)即可添加相同的网站功能。

    示例用法:

    // Existing code that doesn't change:   
    Cookie cookie1=new Cookie("cookie1",Util.encodeURL(id));
    cookie1.setHttpOnly(false);
    cookie1.setPath("/");
    
    Cookie cookie2=new Cookie("cookie2",Util.encodeURL(id));
    cookie2.setHttpOnly(false);
    cookie2.setPath("/");
    
    // Old Code that is replaced by new code
    // httpResponse.addCookie(cookie1);
    // httpResponse.addCookie(cookie2);
    
    // New Code - see static helper class below
    HttpService.addCookie(httpResponse, cookie1, "none");
    HttpService.addCookie(httpResponse, cookie2, "Strict");
    

    使用 cURL 时的示例响应标头:

    < HTTP/1.1 200 OK
    < Connection: keep-alive
    < X-Powered-By: Undertow/1
    < Set-Cookie: cookie1=f871c026e8eb418c9c612f0c7fe05b08; path=/; SameSite=none; secure
    < Set-Cookie: cookie2=51b405b9487f4487b50c80b32eabcc24; path=/; SameSite=Strict; secure
    < Server: WildFly/9
    < Transfer-Encoding: chunked
    < Content-Type: image/png
    < Date: Tue, 10 Mar 2020 01:55:37 GMT
    

    最后是静态辅助类:

    public class HttpService {
        private static final FastDateFormat expiresDateFormat= FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss zzz", TimeZone.getTimeZone("GMT"));
    
    
        public static void addCookie(HttpServletResponse response, Cookie cookie, String sameSite) {
    
            StringBuilder c = new StringBuilder(64+cookie.getValue().length());
    
            c.append(cookie.getName());
            c.append('=');
            c.append(cookie.getValue());
    
            append2cookie(c,"domain",   cookie.getDomain());
            append2cookie(c,"path",     cookie.getPath());
            append2cookie(c,"SameSite", sameSite);
    
            if (cookie.getSecure()) {
                c.append("; secure");
            }
            if (cookie.isHttpOnly()) {
                c.append("; HttpOnly");
            }
            if (cookie.getMaxAge()>=0) {
                append2cookie(c,"Expires", getExpires(cookie.getMaxAge()));
            }
    
            response.addHeader("Set-Cookie", c.toString());
        }
    
        private static String getExpires(int maxAge) {
            if (maxAge<0) {
                return "";
            }
            Calendar expireDate = Calendar.getInstance();
            expireDate.setTime(new Date());
            expireDate.add(Calendar.SECOND,maxAge);
    
            return expiresDateFormat.format(expireDate);
        }
    
        private static void append2cookie(StringBuilder cookie, String key, String value) {
            if (key==null || 
                    value==null || 
                    key.trim().equals("") 
                    || value.trim().equals("")) {
                return;
            }
    
            cookie.append("; ");
            cookie.append(key);
            cookie.append('=');
            cookie.append(value);
        }
    }
    

    【讨论】:

    • 非常感谢您的代码!请注意:如果 cookie 没有 Secure 标志并且设置了 SameSite,它仍然会被忽略 (web.dev/samesite-cookies-explained)。所以我不得不用 if(cookie.getSecure) 包裹 "append2cookie(c,"SameSite", sameSite)"
    【解决方案5】:

    Jetty 服务器版本 9.4.26.v20200117 允许在 cookie 上设置 SameSite 属性。我不得不四处挖掘,但这很管用。

    import static org.eclipse.jetty.http.HttpCookie.SAME_SITE_STRICT_COMMENT;
    
    ...
    
    Cookie cookie = new Cookie("my-cookie", "some-value");
    cookie.setMaxAge(120); // age in seconds
    cookie.setSecure(true);
    cookie.setHttpOnly(true);
    cookie.setComment(SAME_SITE_STRICT_COMMENT);
    
    response.addCookie(cookie);
    

    码头服务器的Response 对象上的addCookie() 方法检查评论以添加SameSite 属性。

    【讨论】:

    • 这很舒服!我爱码头
    • 如何在 Jetty 6.0 等旧版本中进行操作?
    【解决方案6】:

    如果您碰巧使用 Spring Framework,您可以利用 ResponseCookie 类。例如:

    final ResponseCookie responseCookie = ResponseCookie
            .from("<my-cookie-name>", "<my-cookie-value-here>")
            .secure(true)
            .httpOnly(true)
            .path("/auth")
            .maxAge(12345)
            .sameSite("Lax")
            .build();
    response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
    

    声明:提供的标志及其值仅作为类 API 的示例。

    【讨论】:

      【解决方案7】:

      我尝试了使用javax.servlet.http.Cookie 设置SameSite=strict 属性的列出的解决方案,但它们都不起作用。

      但是,这种方式对我有用,使用 javax.servlet.http.Cookie (JRE 1.8 + JBOSS 7.X):

      Cookie cookie = new Cookie(name, value);
      path = path.concat("SameSite=Strict;");
      cookie.setPath(path);
      

      就是这样。测试于

      • Google Chrome 版本 81.0.4044.129(官方版本)(64 位)
      • Microsoft Edge 版本 81.0.416.68(官方版本)(64 位)
      • Firefox 75.0(64 位)

      【讨论】:

      • 谢谢,但如果你有路径,这不起作用,例如路径 / SameSite=None; 导致“为此 cookie 指定了无效路径 ...”。
      • 我会将其修改为Cookie cookie = new Cookie(name, value); path = path.concat("; SameSite=Strict;"); cookie.setPath(path);
      • 它在 tomcat 8.60 下不起作用,它有 Rfc6265CookieProcessor 并且里面有一个对 char ';' 的验证在一条路径中。 cookie 值也一样
      【解决方案8】:

      如果将弹簧靴与汤姆猫一起使用,那么这已在另一个问题中得到解答。 总之,在 tom cat config 上设置属性。这是全球性的,所有 cookie 都将启用相同的站点。 (来自另一个问题https://stackoverflow.com/a/60860531/400048

      @Configuration
      public class MvcConfiguration implements WebMvcConfigurer {
      
        @Bean
        public TomcatContextCustomizer sameSiteCookiesConfig() {
          return context -> {
              final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
              cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
              context.setCookieProcessor(cookieProcessor);
          };
        }
      

      【讨论】:

      • 这确实在我的情况下添加了标题,谢谢伙计。
      【解决方案9】:

      我发现我们在成功退货时创建的 cookie 并没有被“标题编辑”或“标题始终编辑”更改。显然 apache 有两桶 cookie - 见 this

      对我有用的是

      Header onsuccess edit Set-Cookie (.*) "$1; SameSite=Lax"
      

      【讨论】:

        【解决方案10】:

        不使用 spring boot 或 spring session 的解决方案。

        有关解决方案的更多详细信息 Samesite for jessessionId cookie can be set only from response

                package com.cookie.example.filters.cookie;
        
        
          import com.google.common.net.HttpHeaders;
          import org.apache.commons.collections.CollectionUtils;
          import org.apache.commons.lang3.StringUtils;
          import org.springframework.beans.factory.InitializingBean;
          import org.springframework.web.filter.DelegatingFilterProxy;
        
          import javax.annotation.Nonnull;
          import javax.servlet.*;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import javax.servlet.http.HttpServletResponseWrapper;
          import java.io.IOException;
          import java.io.PrintWriter;
          import java.util.Collection;
          import java.util.Collections;
          import java.util.List;
        
          /**
           * Implementation of an HTTP filter {@link Filter} which which allow customization of {@literal Set-Cookie} header.
           * customization is delegated to implementations of {@link CookieHeaderCustomizer}
           */
          public class CookieHeaderCustomizerFilter extends DelegatingFilterProxy implements InitializingBean {
        
            private final List<CookieHeaderCustomizer> cookieHeaderCustomizers;
        
            @Override
            public void afterPropertiesSet() throws ServletException {
              super.afterPropertiesSet();
              if(CollectionUtils.isEmpty(cookieHeaderCustomizers)){
                throw new IllegalArgumentException("cookieHeaderCustomizers is mandatory");
              }
            }
        
            public CookieHeaderCustomizerFilter(final List<CookieHeaderCustomizer> cookieHeaderCustomizers) {
              this.cookieHeaderCustomizers = cookieHeaderCustomizers;
            }
        
            public CookieHeaderCustomizerFilter() {
              this.cookieHeaderCustomizers = Collections.emptyList();
            }
        
        
            /** {@inheritDoc} */
            public void destroy() {
            }
        
            /** {@inheritDoc} */
            public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
              throws IOException, ServletException {
        
              if (!(request instanceof HttpServletRequest)) {
                throw new ServletException("Request is not an instance of HttpServletRequest");
              }
        
              if (!(response instanceof HttpServletResponse)) {
                throw new ServletException("Response is not an instance of HttpServletResponse");
              }
        
              chain.doFilter(request, new CookieHeaderResponseWrapper((HttpServletRequest) request, (HttpServletResponse)response ));
        
            }
        
        
            /**
             * An implementation of the {@link HttpServletResponse} which customize {@literal Set-Cookie}
             */
            private class CookieHeaderResponseWrapper extends HttpServletResponseWrapper{
        
              @Nonnull private final HttpServletRequest request;
        
              @Nonnull private final HttpServletResponse response;
        
        
              public CookieHeaderResponseWrapper(@Nonnull final HttpServletRequest req, @Nonnull final HttpServletResponse resp) {
                super(resp);
                this.request = req;
                this.response = resp;
        
              }
        
              /** {@inheritDoc} */
              @Override
              public void sendError(final int sc) throws IOException {
                applyCustomizers();
                super.sendError(sc);
              }
        
              /** {@inheritDoc} */
              @Override
              public PrintWriter getWriter() throws IOException {
                applyCustomizers();
                return super.getWriter();
              }
        
              /** {@inheritDoc} */
              @Override
              public void sendError(final int sc, final String msg) throws IOException {
                applyCustomizers();
                super.sendError(sc, msg);
              }
        
              /** {@inheritDoc} */
              @Override
              public void sendRedirect(final String location) throws IOException {
                applyCustomizers();
                super.sendRedirect(location);
              }
        
              /** {@inheritDoc} */
              @Override
              public ServletOutputStream getOutputStream() throws IOException {
                applyCustomizers();
                return super.getOutputStream();
              }
        
              private void applyCustomizers(){
        
                final Collection<String> cookiesHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
        
                boolean firstHeader = true;
        
                for (final String cookieHeader : cookiesHeaders) {
        
                  if (StringUtils.isBlank(cookieHeader)) {
                    continue;
                  }
        
                  String customizedCookieHeader = cookieHeader;
        
                  for(CookieHeaderCustomizer cookieHeaderCustomizer : cookieHeaderCustomizers){
        
                    customizedCookieHeader = cookieHeaderCustomizer.customize(request, response, customizedCookieHeader);
        
                  }
        
                  if (firstHeader) {
                    response.setHeader(HttpHeaders.SET_COOKIE,customizedCookieHeader);
                    firstHeader=false;
                  } else {
                    response.addHeader(HttpHeaders.SET_COOKIE, customizedCookieHeader);
                  }
        
                }
        
              }
        
            }
        
          }
        
        
        
          /**
           * Implement this interface and inject add it to {@link SameSiteCookieHeaderCustomizer}
           */
          public interface CookieHeaderCustomizer {
            String customize(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response, @Nonnull final String cookieHeader);
          }
        
        
            package com.cookie.example.filters.cookie;
        
              import org.slf4j.Logger;
              import org.slf4j.LoggerFactory;
        
              import javax.annotation.Nonnull;
              import javax.servlet.http.HttpServletRequest;
              import javax.servlet.http.HttpServletResponse;
        
          /**
           *Add SameSite attribute if not already exist
           *SameSite attribute value is defined by property "cookie.sameSite"
           */
          public class SameSiteCookieHeaderCustomizer implements CookieHeaderCustomizer {
        
            private static final Logger LOGGER = LoggerFactory.getLogger(SameSiteCookieHeaderCustomizer.class);
        
            private static final String SAME_SITE_ATTRIBUTE_NAME ="SameSite";
        
            private static final String SECURE_ATTRIBUTE_NAME="Secure";
        
            private final SameSiteValue sameSiteValue;
        
            public SameSiteCookieHeaderCustomizer(SameSiteValue sameSiteValue) {
              this.sameSiteValue = sameSiteValue;
            }
        
        
            @Override
            public String customize(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response, @Nonnull final String cookieHeader) {
              StringBuilder sb = new StringBuilder(cookieHeader);
              if (!cookieHeader.contains(SAME_SITE_ATTRIBUTE_NAME)) {
                sb.append("; ").append(SAME_SITE_ATTRIBUTE_NAME).append("=").append(sameSiteValue.value);
              }
              if(SameSiteValue.None == sameSiteValue && !cookieHeader.contains(SECURE_ATTRIBUTE_NAME)){
                sb.append("; ").append(SECURE_ATTRIBUTE_NAME);
              }
              return sb.toString();
            }
        
            public enum SameSiteValue{
        
              /**
               * Send the cookie for 'same-site' requests only.
               */
              Strict("Strict"),
              /**
               * Send the cookie for 'same-site' requests along with 'cross-site' top
               * level navigations using safe HTTP methods (GET, HEAD, OPTIONS, and TRACE).
               */
              Lax("Lax"),
              /**
               * Send the cookie for 'same-site' and 'cross-site' requests.
               */
              None("None");
        
              /** The same-site attribute value.*/
              private String value;
        
              /**
               * Constructor.
               *
               * @param attrValue the same-site attribute value.
               */
              SameSiteValue(@Nonnull final String attrValue) {
                value = attrValue;
              }
        
              /**
               * Get the same-site attribute value.
               *
               * @return Returns the value.
               */
              public String getValue() {
                return value;
              }
        
            }
        
          }
        

        【讨论】:

          【解决方案11】:

          ravinder5 确实实现了这一点并将其开源:CookieHeader

          示例用法:

          import com.tgt.egs.auth.cookie.CookieHeader;
          ...
          
          CookieHeader.createSetCookieHeader(cookieName, cookieValue, domain, path, sameSite, secure, httpOnly, expiry);
                  
          

          【讨论】:

            猜你喜欢
            • 2020-11-23
            • 1970-01-01
            • 2020-04-24
            • 1970-01-01
            • 1970-01-01
            • 2021-01-12
            • 2020-12-09
            • 2020-06-07
            • 2020-04-17
            相关资源
            最近更新 更多