如何将SameSite和Secure属性设置为JSESSIONID cookie [英] How to set SameSite and Secure attribute to JSESSIONID cookie

查看:265
本文介绍了如何将SameSite和Secure属性设置为JSESSIONID cookie的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Spring Boot Web应用程序(Spring Boot版本2.0.3.RELEASE),并且在Apache Tomcat 8.5.5服务器中运行.

I have a Spring Boot Web Application (Spring boot version 2.0.3.RELEASE) and running in an Apache Tomcat 8.5.5 server.

使用Google Chrome实施的最新安全策略(自80.0开始淘汰),要求它应用新的 SameSite 属性,以更安全的方式进行跨站点Cookie的访问而不是CSRF.由于我没有进行任何相关操作,并且Chrome设置了第一方Cookie的默认值 SameSite = Lax ,因此我的一个第三方服务集成失败,原因是chrome限制了对当 SameSite = Lax 且第三方响应来自 POST 请求时,跨站点Cookie(O ,因为该过程完成了将第三方服务重定向到我们的具有 POST 请求的网站).在该位置,Tomcat无法找到该会话,因此它在URL的末尾附加了一个新的 JSESSIONID (带有一个新会话,而上一个会话已被终止).因此,Spring拒绝了该URL,因为它包含由新的 JSESSIONID 附加项引入的分号.

With the recent security policy which has imposed by Google Chrome (Rolled out since 80.0), it is requested to apply the new SameSite attribute to make the Cross-site cookie access in a more secure way instead of the CSRF. As I have done nothing related that and Chrome has set default value SameSite=Lax for the first-party cookies, one of my third-party service integration is failing due to the reason that chrome is restricting access of cross-site cookies when SameSite=Lax and if the third party response is coming from a POST request (Once the procedure completes third-party service redirect to our site with a POST request). in there Tomcat unable to find the session so it appends a new JSESSIONID (with a new session and the previous session was killed) at the end of the URL. So Spring rejects the URL as it contains a semicolon which was introduced by the new JSESSIONID append.

所以我需要更改 JSESSIONID cookie属性( SameSite = None; Secure ),并通过多种方法进行尝试,包括WebFilters.在Stackoverflow中尝试了其中的大多数方法,但结果却无济于事.

So I need to change the JSESSIONID cookie attributes(SameSite=None; Secure) and tried it in several ways including WebFilters.I have seen the same question and answers in Stackoverflow and tried most of them but ended up in nowhere.

有人可以提出一个解决方案来更改Spring Boot中的标头吗?

can someone come up with a solution to change those headers in Spring Boot, please?

推荐答案

我能够提出自己的解决方案.

I was able to come up with my own solution for this.

我有两种在Spring Boot上运行的应用程序,它们具有不同的Spring安全配置,并且它们需要不同的解决方案来解决此问题.

I have two kinds of applications which run on Spring boot which has different Spring security configurations and they needed different solutions to fix this.

情况1:无用户身份验证

在这里,您可能已在应用程序中为第三方响应创建了一个端点.在使用控制器方法访问httpSession之前,您是安全的.如果您以不同的控制器方法访问会话,则向该处发送一个临时重定向请求,如下所示.

In here you might have created an endpoint for the 3rd party response, in your application. You are safe until you access httpSession in a controller method. If you are accessing session in different controller method then send a temporary redirect request to there like follows.

@Controller
public class ThirdPartyResponseController{

@RequestMapping(value=3rd_party_response_URL, method=RequestMethod.POST)
public void thirdPartyresponse(HttpServletRequest request, HttpServletResponse httpServletResponse){
    // your logic
    // and you can set any data as an session attribute which you want to access over the 2nd controller 
    request.getSession().setAttribute(<data>)
    try {
        httpServletResponse.sendRedirect(<redirect_URL>);
    } catch (IOException e) {
        // handle error
    }
}

@RequestMapping(value=redirect_URL, method=RequestMethod.GET)
public String thirdPartyresponse(HttpServletRequest request,  HttpServletResponse httpServletResponse, Model model,  RedirectAttributes redirectAttributes, HttpSession session){
    // your logic
        return <to_view>;
    }
}

仍然,您需要在安全配置中允许3rd_party_response_url.

Still, you need to allow the 3rd_party_response_url in your security configuration.

情况2:用户需要通过身份验证/登录

在Spring Web应用程序中,您可以通过

In a Spring Web application where you have configured most of your security rules either through HttpSecurity or WebSecurity, check this solution.

我已经测试了该解决方案的示例安全配置:

Sample security config which I have tested the solution:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
          ......
          ..antMatchers(<3rd_party_response_URL>).permitAll();
          .....
          ..csrf().ignoringAntMatchers(<3rd_party_response_URL>);
    }
}

在此配置中我要强调的要点是,您应允许Spring Security和CSRF保护(如果已启用)中的第三方响应URL.

The Important points which I want to highlight in this configuration are you should allow the 3rd party response URL from Spring Security and CSRF protection(if it's enabled).

然后,我们需要通过扩展 Filter 类对我不起作用),并通过拦截每个 HttpServletRequest来将 SameSite 属性设置为 JSESSIONID cookie并设置响应标题.

Then we need to create a HttpServletRequest Filter by extending GenericFilterBean class (Filter class did not work for me) and setting the SameSite Attributes to the JSESSIONID cookie by intercepting each HttpServletRequest and setting the response headers.

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class SessionCookieFilter extends GenericFilterBean {

    private final List<String> PATHS_TO_IGNORE_SETTING_SAMESITE = Arrays.asList("resources", <add other paths you want to exclude>);
    private final String SESSION_COOKIE_NAME = "JSESSIONID";
    private final String SAME_SITE_ATTRIBUTE_VALUES = ";HttpOnly;Secure;SameSite=None";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String requestUrl = req.getRequestURL().toString();
        boolean isResourceRequest = requestUrl != null ? StringUtils.isNoneBlank(PATHS_TO_IGNORE_SETTING_SAMESITE.stream().filter(s -> requestUrl.contains(s)).findFirst().orElse(null)) : null;
        if (!isResourceRequest) {
            Cookie[] cookies = ((HttpServletRequest) request).getCookies();
            if (cookies != null && cookies.length > 0) {
                List<Cookie> cookieList = Arrays.asList(cookies);
                Cookie sessionCookie = cookieList.stream().filter(cookie -> SESSION_COOKIE_NAME.equals(cookie.getName())).findFirst().orElse(null);
                if (sessionCookie != null) {
                    resp.setHeader(HttpHeaders.SET_COOKIE, sessionCookie.getName() + "=" + sessionCookie.getValue() + SAME_SITE_ATTRIBUTE_VALUES);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

然后通过以下方式将此过滤器添加到Spring Security过滤器链中:

Then add this filter to the Spring Security filter chain by

@Override
protected void configure(HttpSecurity http) throws Exception {
        http.
           ....
           .addFilterAfter(new SessionCookieFilter(), BasicAuthenticationFilter.class);
}

SameSite Cookie属性将不支持某些旧版浏览器,在这种情况下,请检查浏览器并避免在不兼容的客户端中设置 SameSite ..

This SameSite cookie attribute will not support some old browser versions and in that case, check the browser and avoid setting SameSite in incompatible clients..

private static final String _I_PHONE_IOS_12 = "iPhone OS 12_";
    private static final String _I_PAD_IOS_12 = "iPad; CPU OS 12_";
    private static final String _MAC_OS_10_14 = " OS X 10_14_";
    private static final String _VERSION = "Version/";
    private static final String _SAFARI = "Safari";
    private static final String _EMBED_SAFARI = "(KHTML, like Gecko)";
    private static final String _CHROME = "Chrome/";
    private static final String _CHROMIUM = "Chromium/";
    private static final String _UC_BROWSER = "UCBrowser/";
    private static final String _ANDROID = "Android";

    /*
     * checks SameSite=None;Secure incompatible Browsers
     * https://www.chromium.org/updates/same-site/incompatible-clients
     */
    public static boolean isSameSiteInCompatibleClient(HttpServletRequest request) {
        String userAgent = request.getHeader("user-agent");
        if (StringUtils.isNotBlank(userAgent)) {
            boolean isIos12 = isIos12(userAgent), isMacOs1014 = isMacOs1014(userAgent), isChromeChromium51To66 = isChromeChromium51To66(userAgent), isUcBrowser = isUcBrowser(userAgent);
            //TODO : Added for testing purpose. remove before Prod release.
            LOG.info("*********************************************************************************");
            LOG.info("is iOS 12 = {}, is MacOs 10.14 = {}, is Chrome 51-66 = {}, is Android UC Browser = {}", isIos12, isMacOs1014, isChromeChromium51To66, isUcBrowser);
            LOG.info("*********************************************************************************");
            return isIos12 || isMacOs1014 || isChromeChromium51To66 || isUcBrowser;
        }
        return false;
    }

    private static boolean isIos12(String userAgent) {
        return StringUtils.contains(userAgent, _I_PHONE_IOS_12) || StringUtils.contains(userAgent, _I_PAD_IOS_12);
    }

    private static boolean isMacOs1014(String userAgent) {
        return StringUtils.contains(userAgent, _MAC_OS_10_14)
            && ((StringUtils.contains(userAgent, _VERSION) && StringUtils.contains(userAgent, _SAFARI))  //Safari on MacOS 10.14
            || StringUtils.contains(userAgent, _EMBED_SAFARI)); // Embedded browser on MacOS 10.14
    }

    private static boolean isChromeChromium51To66(String userAgent) {
        boolean isChrome = StringUtils.contains(userAgent, _CHROME), isChromium = StringUtils.contains(userAgent, _CHROMIUM);
        if (isChrome || isChromium) {
            int version = isChrome ? Integer.valueOf(StringUtils.substringAfter(userAgent, _CHROME).substring(0, 2))
                : Integer.valueOf(StringUtils.substringAfter(userAgent, _CHROMIUM).substring(0, 2));
            return ((version >= 51) && (version <= 66));    //Chrome or Chromium V51-66
        }
        return false;
    }

    private static boolean isUcBrowser(String userAgent) {
        if (StringUtils.contains(userAgent, _UC_BROWSER) && StringUtils.contains(userAgent, _ANDROID)) {
            String[] version = StringUtils.splitByWholeSeparator(StringUtils.substringAfter(userAgent, _UC_BROWSER).substring(0, 7), ".");
            int major = Integer.valueOf(version[0]), minor = Integer.valueOf(version[1]), build = Integer.valueOf(version[2]);
            return ((major != 0) && ((major < 12) || (major == 12 && (minor < 13)) || (major == 12 && minor == 13 && (build < 2)))); //UC browser below v12.13.2 in android
        }
        return false;
    }

在SessionCookieFilter中添加上述检查,如下所示,

Add above check in SessionCookieFilter like follows,

if (!isResourceRequest && !UserAgentUtils.isSameSiteInCompatibleClient(req)) {

此过滤器在本地主机环境中不起作用,因为它需要安全(HTTPS)连接来设置 Secure cookie属性.

This filter won't work in localhost environments as it requires a Secured(HTTPS) connection to set Secure cookie attribute.

有关详细说明,请阅读

For a detailed explanation read this blog post.

这篇关于如何将SameSite和Secure属性设置为JSESSIONID cookie的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆