Java应用程序中的SameSite cookie

时间:2017-03-10 11:23:49

标签: java cookies csrf flags

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

11 个答案:

答案 0 :(得分:29)

我不是JEE专家,但我认为因为那个cookie属性是一个有点新的发明,所以你不能指望它出现在Java EE 7接口或实现中。似乎Cookie类缺少通用属性的setter。但不是通过

将Cookie添加到HttpServletResponse
response.addCookie(myCookie)

您只需通过

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

我希望这对你来说已经足够了。

P.S。:我已根据您的问题删除了我最近的一些评论,也许您还想删除现在不再需要的3条回复。随后我也可以在这里删除这一段。感谢。

答案 1 :(得分:7)

截止到今天(20.01.20),servlet-api不允许为Cookie设置sameSite属性。顺便说一句,正在进行中的票证(LINK)将发布新票证(5.0或5.1 servlet-api)。

选项1:您并不着急,可以等待servlet-api版本,其中Cookie类和SessionCookieConfig类具有专用的方法来设置{ {1}}属性。

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

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

此处理器的用法非常简单。在context.xml内部:

Cookie Processor Component

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

答案 2 :(得分:5)

如果您已有代码,那么无疑已经使用了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);
    }
}

答案 3 :(得分:2)

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);

码头服务器的addCookie()对象上的Response方法对注释进行检查,以添加SameSite属性。

答案 4 :(得分:2)

我尝试使用列出的解决方案使用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位)

答案 5 :(得分:2)

如果将弹簧靴与Tom猫一起使用,则已在另一个问题中得到了回答。 总而言之,在tom cat配置上设置属性。这是全局的,所有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);
    };
  }

答案 6 :(得分:2)

如果您碰巧使用 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 :(得分:1)

如果您不想更新所有代码,也可以使用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

答案 8 :(得分:1)

我发现,成功返回时创建的cookie未被“标题编辑”或“标题始终编辑”更改。显然,apache有两个Cookie桶-参见this

对我有用的是

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

答案 9 :(得分:0)

不使用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;
      }

    }

  }

答案 10 :(得分:0)

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

示例用法:

import com.tgt.egs.auth.cookie.CookieHeader;
...

CookieHeader.createSetCookieHeader(cookieName, cookieValue, domain, path, sameSite, secure, httpOnly, expiry);