Java 向JSSessionID Spring Security添加相同的站点头
Google chrome引入了需要设置相同站点标题的更改。为了实现这一点,我添加了一个自定义过滤器,如下所示:Java 向JSSessionID Spring Security添加相同的站点头,java,spring,spring-boot,spring-security,Java,Spring,Spring Boot,Spring Security,Google chrome引入了需要设置相同站点标题的更改。为了实现这一点,我添加了一个自定义过滤器,如下所示: public class SameSiteFilter extends GenericFilterBean { private Logger LOG = LoggerFactory.getLogger(SameSiteFilter.class); @Override public void doFilter(ServletRequest request,
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;
}
}
然而,当我查看接收到的头时,我得到了以下结果
过滤器在所有响应中添加必填字段,但包含JSESSIONID cookie的响应除外。如何将标题添加到此cookie。我尝试配置tomcat设置,但我们将代码部署为WAR文件,因此也没有成功。为了解决这个问题,我添加了一个过滤器,用于筛选所有响应。这是相同的代码
@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);
}
}
@组件
公共类SameSiteFilter实现过滤器{
私有记录器LOG=LoggerFactory.getLogger(SameSiteFilter.class);
@凌驾
public void init(final FilterConfig FilterConfig)抛出ServletException{
info(“同一站点筛选器初始化筛选器:{}”,this);
}
@凌驾
public void doFilter(最终ServletRequest请求、最终ServletResponse响应、最终FilterChain链)抛出IOException、ServletException{
HttpServletRequest req=(HttpServletRequest)请求;
HttpServletResponse res=(HttpServletResponse)响应;
info(“同一站点筛选器日志记录响应:{}”,res.getContentType());
集合头=res.getHeaders(HttpHeaders.SET\u COOKIE);
布尔值firstHeader=true;
对于(字符串头:头){//可以有多个集Cookie属性
如果(第一个标题){
res.setHeader(HttpHeaders.SET_COOKIE,String.format(“%s;%s”,header,“SameSite=None”);
LOG.info(String.format(“同一站点筛选器第一个标头%s;%s”,标头,“SameSite=None;Secure”);
firstHeader=false;
继续;
}
res.addHeader(HttpHeaders.SET_COOKIE,String.format(“%s;%s”,header,“SameSite=None”);
LOG.info(String.format(“同一站点筛选器剩余头%s;%s”,头,“SameSite=None;Secure”);
}
链式过滤器(要求、恢复);
}
@凌驾
公共空间销毁(){
warn(“同一站点筛选器破坏筛选器:{}”,this);
}
}
这允许在包含cookie的响应中添加所需的头文件为了避免这个问题,我添加了一个筛选所有响应的过滤器。这是相同的代码
@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);
}
}
@组件
公共类SameSiteFilter实现过滤器{
私有记录器LOG=LoggerFactory.getLogger(SameSiteFilter.class);
@凌驾
public void init(final FilterConfig FilterConfig)抛出ServletException{
info(“同一站点筛选器初始化筛选器:{}”,this);
}
@凌驾
public void doFilter(最终ServletRequest请求、最终ServletResponse响应、最终FilterChain链)抛出IOException、ServletException{
HttpServletRequest req=(HttpServletRequest)请求;
HttpServletResponse res=(HttpServletResponse)响应;
info(“同一站点筛选器日志记录响应:{}”,res.getContentType());
集合头=res.getHeaders(HttpHeaders.SET\u COOKIE);
布尔值firstHeader=true;
对于(字符串头:头){//可以有多个集Cookie属性
如果(第一个标题){
res.setHeader(HttpHeaders.SET_COOKIE,String.format(“%s;%s”,header,“SameSite=None”);
LOG.info(String.format(“同一站点筛选器第一个标头%s;%s”,标头,“SameSite=None;Secure”);
firstHeader=false;
继续;
}
res.addHeader(HttpHeaders.SET_COOKIE,String.format(“%s;%s”,header,“SameSite=None”);
LOG.info(String.format(“同一站点筛选器剩余头%s;%s”,头,“SameSite=None;Secure”);
}
链式过滤器(要求、恢复);
}
@凌驾
公共空间销毁(){
warn(“同一站点筛选器破坏筛选器:{}”,this);
}
}
这允许在包含cookie的响应中添加所需的头
不使用spring引导或spring会话的解决方案
有关解决方案的详细信息,请参阅
package com.cookie.example.filters.cookie;
导入com.google.common.net.HttpHeaders;
导入org.apache.commons.collections.CollectionUtils;
导入org.apache.commons.lang3.StringUtils;
导入org.springframework.beans.factory.initializebean;
导入org.springframework.web.filter.DelegatingFilterProxy;
导入javax.annotation.Nonnull;
导入javax.servlet.*;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpServletResponse;
导入javax.servlet.http.HttpServletResponseWrapper;
导入java.io.IOException;
导入java.io.PrintWriter;
导入java.util.Collection;
导入java.util.Collections;
导入java.util.List;
/**
*HTTP筛选器{@link filter}的实现,该筛选器允许自定义{@literal Set Cookie}头。
*自定义委托给{@link CookieHeaderCustomizer}的实现
*/
公共类CookieHeaderCustomizerFilter扩展DelegatingFilterProxy实现初始化bean{
私人最终名单cookieHeaderCustomizers;
@凌驾
在PropertieSet()引发ServletException后公共无效{
super.afterPropertiesSet();
if(CollectionUtils.isEmpty(cookieHeaderCustomizers)){
抛出新的IllegalArgumentException(“cookieHeaderCustomizers是强制性的”);
}
}
公共CookieHeaderCustomizerFilter(最终列表CookieHeaderCustomizer){
this.cookieHeaderCustomizers=cookieHeaderCustomizers;
}
公共CookieHeaderCustomizerFilter(){
this.cookieHeaderCustomizers=Collections.emptyList();
}
/**{@inheritardoc}*/
公共空间销毁(){
}
/**{@inheritardoc}*/
公共无效doFilter(最终ServletRequest请求,最终Se
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;
}
}
}