【问题标题】:Adding Same Site Header to JSESSIONID Spring Security将相同的站点标题添加到 JSESSIONID Spring Security
【发布时间】:2020-07-06 23:46:58
【问题描述】:

Google chrome 引入了需要设置 Same-Site 标头的更改。为了实现这一点,我添加了一个自定义过滤器,如下所示,

public class SameSiteFilter extends GenericFilterBean {
    private Logger LOG = LoggerFactory.getLogger(SameSiteFilter.class);

    @Override
    public void doFilter(ServletRequest request,  ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)response;
        response = addSameSiteCookieAttribute((HttpServletResponse) response);
        chain.doFilter(request, response);
    }    

    private HttpServletResponse addSameSiteCookieAttribute(HttpServletResponse response) {
        Collection<String> header = response.getHeaders(HttpHeaders.SET_COOKIE);
        LOG.info(String.format("%s; %s", header, "SameSite=None; Secure"));
        response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None; Secure"));

        return response;
    }
}

以下是安全配置的代码

@Configuration
@EnableWebMvcSecurity
public class CustomSecurityConfiguration extends WebSecurityConfigurerAdapter { 
    @Autowired
    private OnyxUserDetailsService onyxUserDetailsService;

    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/rest/user", "/info/**/*","/rest/version/check")
                .permitAll().antMatchers("/data/**/*")
                .access("hasRole('ROLE_ADMIN')").anyRequest()
                .fullyAuthenticated().and().httpBasic().realmName("ADOBENET")
                .and().logout().
                logoutSuccessHandler((new LogoutSuccessHandler() {

                    @Override
                    public void onLogoutSuccess(HttpServletRequest request,
                            HttpServletResponse response, Authentication authentication)
                            throws IOException, ServletException {
                        response.setStatus(HttpStatus.OK.value());
                        response.getWriter().flush();
                    }
                })).deleteCookies("JSESSIONID", "XSRF-TOKEN")
                .invalidateHttpSession(true).logoutUrl("/rest/logout")
                .logoutSuccessUrl("/rest/user").and()
                .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)   
                .addFilterAfter(new SameSiteFilter(), BasicAuthenticationFilter.class)          
                .csrf().disable();
    }

    @Override
    @Order(Ordered.HIGHEST_PRECEDENCE)
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.authenticationProvider(customAuthenticationProvider);
    }
}

但是,当我查看收到的标头时,我明白了

过滤器在包含 JSESSIONID cookie 的所有响应异常中添加必填字段。如何将标头添加到此 cookie。我尝试配置 tomcat 设置,但我们将代码部署为 WAR 文件,所以这也不起作用。

【问题讨论】:

标签: java spring spring-boot spring-security


【解决方案1】:

为了解决这个问题,我添加了一个过滤器来筛选所有响应。这是相同的代码,

@Component
public class SameSiteFilter implements Filter {
    private Logger LOG = LoggerFactory.getLogger(SameSiteFilter.class);

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        LOG.info("Same Site Filter Initializing filter :{}", this);
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        LOG.info("Same Site Filter Logging Response :{}", res.getContentType());

        Collection<String> headers = res.getHeaders(HttpHeaders.SET_COOKIE);
        boolean firstHeader = true;
        for (String header : headers) { // there can be multiple Set-Cookie attributes
            if (firstHeader) {
                res.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s",  header, "SameSite=None"));
                LOG.info(String.format("Same Site Filter First Header %s; %s", header, "SameSite=None; Secure"));

                firstHeader = false;
                continue;
            }

            res.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s",  header, "SameSite=None"));
            LOG.info(String.format("Same Site Filter Remaining Headers %s; %s", header, "SameSite=None; Secure"));
        }

        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        LOG.warn("Same Site Filter Destructing filter :{}", this);
    }
}

这允许在包含 cookie 的响应中添加所需的标头

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2020-05-19
      • 2013-09-23
      • 2017-08-17
      • 2017-02-27
      • 2019-06-25
      • 2017-01-08
      • 2014-09-29
      • 2019-04-08
      相关资源
      最近更新 更多