如何为 jsessionid cookie 启用相同站点 [英] How to enable samesite for jsessionid cookie

查看:27
本文介绍了如何为 jsessionid cookie 启用相同站点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何为在 wildfly 上运行的 Web 应用程序启用同一个站点.检查了 standalone.xml 但是在

中找不到合适的标签

<session-cookie http-only="true";安全=真"/><jsp-config/></servlet-容器>

解决方案

目前 Java Servlet 4.0 规范不支持 SameSite cookie 属性.您可以通过打开 javax.servlet.http.Cookie<查看可用属性/a> java 类.

但是,有几种解决方法.您可以手动覆盖 Set-Cookie 属性.

方法 #1(使用自定义 Spring HttpFirewall 和请求包装器):

您需要在创建会话后立即包装请求并调整 cookie.您可以通过定义以下类来实现它:

一个 bean(如果你想把所有东西都放在一个地方,你可以在 SecurityConfig 中定义它.为了简洁,我只是在上面加上了 @Component 注释)

package hello.approach1;导入 javax.servlet.http.HttpServletRequest;导入 javax.servlet.http.HttpServletResponse;导入 org.springframework.security.web.firewall.FirewalledRequest;导入 org.springframework.security.web.firewall.HttpFirewall;导入 org.springframework.security.web.firewall.RequestRejectedException;导入 org.springframework.stereotype.Component;@零件公共类 CustomHttpFirewall 实现 HttpFirewall {@覆盖public FirewalledRequest getFirewalledRequest(HttpServletRequest request) 抛出 RequestRejectedException {返回新的 RequestWrapper(request);}@覆盖公共 HttpServletResponse getFirewalledResponse(HttpServletResponse 响应) {返回新的 ResponseWrapper(响应);}}

第一个包装类

package hello.approach1;导入 java.util.Collection;导入 javax.servlet.http.HttpServletRequest;导入 javax.servlet.http.HttpServletResponse;导入 javax.servlet.http.HttpSession;导入 org.springframework.http.HttpHeaders;导入 org.springframework.security.web.firewall.FirewalledRequest;导入 org.springframework.web.context.request.RequestContextHolder;导入 org.springframework.web.context.request.ServletRequestAttributes;/*** 围绕 HttpServletRequest 的包装,覆盖 Set-Cookie 响应标头并添加 SameSite=None 部分.*/公共类 RequestWrapper 扩展 FirewalledRequest {/*** 构造一个包装给定请求的请求对象.** @param request 要包装的请求* @throws IllegalArgumentException 如果请求为空*/公共请求包装器(HttpServletRequest 请求){超级(请求);}/*** 在 Spring Boot 中默认必须为空.请参阅防火墙请求.*/@覆盖公共无效重置(){}@覆盖公共 HttpSession getSession(布尔创建){HttpSession session = super.getSession(create);如果(创建){ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();如果 (ra != null) {overwriteSetCookie(ra.getResponse());}}返回会话;}@覆盖公共字符串 changeSessionId() {String newSessionId = super.changeSessionId();ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();如果 (ra != null) {overwriteSetCookie(ra.getResponse());}返回新会话 ID;}私有无效覆盖SetCookie(HttpServletResponse响应){如果(响应!= null){集合<字符串>headers = response.getHeaders(HttpHeaders.SET_COOKIE);布尔 firstHeader = true;for (String header : headers) {//可以有多个 Set-Cookie 属性如果(第一个标题){response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None"));//放firstHeader = 假;继续;}response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None"));//添加}}}}

第二个包装类

package hello.approach1;导入 javax.servlet.http.HttpServletResponse;导入 javax.servlet.http.HttpServletResponseWrapper;/*** 虚拟实现.* 与 RequestWrapper 保持一致.*/公共类 ResponseWrapper 扩展 HttpServletResponseWrapper {/*** 构造一个包装给定响应的响应适配器.** @param response 要包装的响应* @throws IllegalArgumentException 如果响应为空*/公共 ResponseWrapper(HttpServletResponse 响应){超级(响应);}}

方法 #2(使用 Spring 的 AuthenticationSuccessHandler):

<块引用>

此方法不适用于基本身份验证.在基本身份验证的情况下,在控制器返回响应对象后立即刷新/提交响应,然后调用 AuthenticationSuccessHandlerImpl#addSameSiteCookieAttribute.

package hello.approach2;导入 java.io.IOException;导入 java.util.Collection;导入 javax.servlet.http.HttpServletRequest;导入 javax.servlet.http.HttpServletResponse;导入 org.springframework.http.HttpHeaders;导入 org.springframework.security.core.Authentication;导入 org.springframework.security.web.authentication.AuthenticationSuccessHandler;公共类 AuthenticationSuccessHandlerImpl 实现 AuthenticationSuccessHandler {@覆盖public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 抛出 IOException {addSameSiteCookieAttribute(响应);//添加 SameSite=strict 到 Set-Cookie 属性response.sendRedirect("/hello");//认证成功后重定向到hello.html}私有无效 addSameSiteCookieAttribute(HttpServletResponse 响应){集合<字符串>headers = response.getHeaders(HttpHeaders.SET_COOKIE);布尔 firstHeader = true;for (String header : headers) {//可以有多个 Set-Cookie 属性如果(第一个标题){response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));firstHeader = 假;继续;}response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));}}}

方法 #3(使用 javax.servlet.Filter):

<块引用>

此方法不适用于基本身份验证.在基本身份验证的情况下,在控制器返回响应对象后立即刷新/提交响应,然后调用 SameSiteFilter#addSameSiteCookieAttribute.

package hello.approach3;导入 java.io.IOException;导入 java.util.Collection;导入 javax.servlet.FilterChain;导入 javax.servlet.FilterConfig;导入 javax.servlet.ServletException;导入 javax.servlet.ServletRequest;导入 javax.servlet.ServletResponse;导入 javax.servlet.http.HttpServletResponse;导入 org.springframework.http.HttpHeaders;公共类 SameSiteFilter 实现 javax.servlet.Filter {@覆盖public void init(FilterConfig filterConfig) 抛出 ServletException {}@覆盖public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 抛出 IOException, ServletException {chain.doFilter(请求,响应);addSameSiteCookieAttribute((HttpServletResponse) 响应);//添加 SameSite=strict cookie 属性}私有无效 addSameSiteCookieAttribute(HttpServletResponse 响应){集合<字符串>headers = response.getHeaders(HttpHeaders.SET_COOKIE);布尔 firstHeader = true;for (String header : headers) {//可以有多个 Set-Cookie 属性如果(第一个标题){response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));firstHeader = 假;继续;}response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));}}@覆盖公共无效销毁(){}}

方法 #4(如果您使用的是 Tomcat 9.0.21/Tomcat 8.5.42 或更高版本)

在您的 Web 应用程序中,在 META-INF 文件夹内创建一个 context.xml 文件,其中包含以下内容:

<CookieProcessor sameSiteCookies="strict";/>

从 Tomcat 9.0.28/Tomcat 8.5.48 开始,可以将 SameSite 设置为 none)

有关详细信息,请参阅此拉取请求.

演示项目

您可以在 GitHub 上查看这个演示项目,了解更多详情前 3 种方法的配置.

SecurityConfig 包含所有必要的配置.

<块引用>

不能保证使用 addHeader 工作,因为基本上Servlet 容器管理着 Session 和 Cookie 的创建.为了例如,如果您返回 JSON,则第二种和第三种方法将不起作用响应正文,因为应用程序服务器将覆盖 Set-Cookie响应刷新期间的标头.但是,第二种和第三种方法将在成功后将用户重定向到另一个页面的情况下工作身份验证.

<块引用>

请注意 Postman 不呈现/支持 Cookies 部分下的 SameSite cookie 属性(至少在撰写本文时).您可以查看 Set-Cookie 响应头或使用 curl 查看是否添加了 SameSite cookie 属性.

How can I enable samesite for my web application which runs on wildfly as. Checked standalone.xml however could not find an appropriate tag within

<servlet-container name="default">
    <session-cookie http-only="true" secure="true"/>
    <jsp-config/>
</servlet-container>

解决方案

As for now the Java Servlet 4.0 specification doesn't support the SameSite cookie attribute. You can see available attributes by opening javax.servlet.http.Cookie java class.

However, there are a couple of workarounds. You can override Set-Cookie attribute manually.

Approach #1 (using custom Spring HttpFirewall and wrapper around request):

You need to wrap request and adjust cookies right after session is created. You can achieve it by defining the following classes:

one bean (You can define it inside SecurityConfig if you want to hold everything in one place. I just put @Component annotation on it for brevity)

package hello.approach1;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Component;

@Component
public class CustomHttpFirewall implements HttpFirewall {

    @Override
    public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        return new RequestWrapper(request);
    }

    @Override
    public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
        return new ResponseWrapper(response);
    }

}

first wrapper class

package hello.approach1;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Wrapper around HttpServletRequest that overwrites Set-Cookie response header and adds SameSite=None portion.
 */
public class RequestWrapper extends FirewalledRequest {

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public RequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * Must be empty by default in Spring Boot. See FirewalledRequest.
     */
    @Override
    public void reset() {
    }

    @Override
    public HttpSession getSession(boolean create) {
        HttpSession session = super.getSession(create);

        if (create) {
            ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (ra != null) {
                overwriteSetCookie(ra.getResponse());
            }
        }

        return session;
    }

    @Override
    public String changeSessionId() {
        String newSessionId = super.changeSessionId();
        ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ra != null) {
            overwriteSetCookie(ra.getResponse());
        }
        return newSessionId;
    }

    private void overwriteSetCookie(HttpServletResponse response) {
        if (response != null) {
            Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
            boolean firstHeader = true;
            for (String header : headers) { // there can be multiple Set-Cookie attributes
                if (firstHeader) {
                    response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // set
                    firstHeader = false;
                    continue;
                }
                response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // add
            }
        }
    }
}

second wrapper class

package hello.approach1;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 * Dummy implementation.
 * To be aligned with RequestWrapper.
 */
public class ResponseWrapper extends HttpServletResponseWrapper {
    /**
     * Constructs a response adaptor wrapping the given response.
     *
     * @param response The response to be wrapped
     * @throws IllegalArgumentException if the response is null
     */
    public ResponseWrapper(HttpServletResponse response) {
        super(response);
    }
}

Approach #2 (using Spring's AuthenticationSuccessHandler):

This approach doesn't work for basic authentication. In case basic authentication, response is flushed/committed right after controller returns response object, before AuthenticationSuccessHandlerImpl#addSameSiteCookieAttribute is called.

package hello.approach2;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        addSameSiteCookieAttribute(response);    // add SameSite=strict to Set-Cookie attribute
        response.sendRedirect("/hello"); // redirect to hello.html after success auth
    }

    private void addSameSiteCookieAttribute(HttpServletResponse response) {
        Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
        boolean firstHeader = true;
        for (String header : headers) { // there can be multiple Set-Cookie attributes
            if (firstHeader) {
                response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
                firstHeader = false;
                continue;
            }
            response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
        }
    }
}

Approach #3 (using javax.servlet.Filter):

This approach doesn't work for basic authentication. In case basic authentication, response is flushed/committed right after controller returns response object, before SameSiteFilter#addSameSiteCookieAttribute is called.

package hello.approach3;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;

public class SameSiteFilter implements javax.servlet.Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
        addSameSiteCookieAttribute((HttpServletResponse) response); // add SameSite=strict cookie attribute
    }

    private void addSameSiteCookieAttribute(HttpServletResponse response) {
        Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
        boolean firstHeader = true;
        for (String header : headers) { // there can be multiple Set-Cookie attributes
            if (firstHeader) {
                response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
                firstHeader = false;
                continue;
            }
            response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
        }
    }

    @Override
    public void destroy() {

    }
}

Approach #4 (if you are using Tomcat 9.0.21 / Tomcat 8.5.42 or above versions)

In your web application, inside the META-INF folder create a context.xml file with the following inside:

<Context>
   <CookieProcessor sameSiteCookies="strict" />
</Context>

Setting the SameSite to none is available starting from Tomcat 9.0.28 / Tomcat 8.5.48)

See this pull request for more details.

Demo project

You can look at this demo project on the GitHub for more details on the configuration for the first 3 approaches.

The SecurityConfig contains all the necessary configuration.

Using addHeader is not guaranteed to work because basically the Servlet container manages the creation of the Session and Cookie. For example, the second and third approaches won't work in case you return JSON in response body because application server will overwrite Set-Cookie header during flushing of response. However, second and third approaches will work in cases, when you redirect a user to another page after successful authentication.

Pay attention that Postman doesn't render/support SameSite cookie attribute under Cookies section (at least at the time of writing). You can look at Set-Cookie response header or use curl to see if SameSite cookie attribute was added.

这篇关于如何为 jsessionid cookie 启用相同站点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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