diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java index d7a69bf5a7..bafd8733b8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ package org.springframework.http.server.reactive; import java.nio.file.Path; -import java.util.List; import io.netty5.buffer.Buffer; import io.netty5.channel.ChannelId; +import io.netty5.handler.codec.http.headers.DefaultHttpSetCookie; +import io.netty5.handler.codec.http.headers.HttpSetCookie; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; @@ -106,11 +107,13 @@ class ReactorNetty2ServerHttpResponse extends AbstractServerHttpResponse impleme @Override protected void applyCookies() { - // Netty Cookie doesn't support sameSite. When this is resolved, we can adapt to it again: - // https://github.com/netty/netty/issues/8161 - for (List cookies : getCookies().values()) { - for (ResponseCookie cookie : cookies) { - this.response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + for (String name : getCookies().keySet()) { + for (ResponseCookie httpCookie : getCookies().get(name)) { + Long maxAge = (!httpCookie.getMaxAge().isNegative()) ? httpCookie.getMaxAge().getSeconds() : null; + HttpSetCookie.SameSite sameSite = (httpCookie.getSameSite() != null) ? HttpSetCookie.SameSite.valueOf(httpCookie.getSameSite()) : null; + DefaultHttpSetCookie cookie = new DefaultHttpSetCookie(name, httpCookie.getValue(), httpCookie.getPath(), + httpCookie.getDomain(), null, maxAge, sameSite, false, httpCookie.isSecure(), httpCookie.isHttpOnly()); + this.response.addCookie(cookie); } } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java index c3e5f1d84f..c73ea0dda9 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,12 @@ package org.springframework.http.server.reactive; import java.nio.file.Path; -import java.util.List; import java.util.Objects; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelId; +import io.netty.handler.codec.http.cookie.CookieHeaderNames; +import io.netty.handler.codec.http.cookie.DefaultCookie; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; @@ -105,11 +106,24 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze @Override protected void applyCookies() { - // Netty Cookie doesn't support sameSite. When this is resolved, we can adapt to it again: - // https://github.com/netty/netty/issues/8161 - for (List cookies : getCookies().values()) { - for (ResponseCookie cookie : cookies) { - this.response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + for (String name : getCookies().keySet()) { + for (ResponseCookie httpCookie : getCookies().get(name)) { + DefaultCookie cookie = new DefaultCookie(name, httpCookie.getValue()); + if (!httpCookie.getMaxAge().isNegative()) { + cookie.setMaxAge(httpCookie.getMaxAge().getSeconds()); + } + if (httpCookie.getDomain() != null) { + cookie.setDomain(httpCookie.getDomain()); + } + if (httpCookie.getPath() != null) { + cookie.setPath(httpCookie.getPath()); + } + cookie.setSecure(httpCookie.isSecure()); + cookie.setHttpOnly(httpCookie.isHttpOnly()); + if (httpCookie.getSameSite() != null) { + cookie.setSameSite(CookieHeaderNames.SameSite.valueOf(httpCookie.getSameSite())); + } + this.response.addCookie(cookie); } } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index 0e34c6b725..2e52b6ed1e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,13 @@ package org.springframework.http.server.reactive; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.List; import jakarta.servlet.AsyncContext; import jakarta.servlet.AsyncEvent; import jakarta.servlet.AsyncListener; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.WriteListener; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; @@ -164,18 +164,24 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { @Override protected void applyCookies() { - // Servlet Cookie doesn't support same site: - // https://github.com/eclipse-ee4j/servlet-api/issues/175 - - // For Jetty, starting 9.4.21+ we could adapt to HttpCookie: - // https://github.com/eclipse/jetty.project/issues/3040 - - // For Tomcat, it seems to be a global option only: - // https://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html - - for (List cookies : getCookies().values()) { - for (ResponseCookie cookie : cookies) { - this.response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + for (String name : getCookies().keySet()) { + for (ResponseCookie httpCookie : getCookies().get(name)) { + Cookie cookie = new Cookie(name, httpCookie.getValue()); + if (!httpCookie.getMaxAge().isNegative()) { + cookie.setMaxAge((int) httpCookie.getMaxAge().getSeconds()); + } + if (httpCookie.getDomain() != null) { + cookie.setDomain(httpCookie.getDomain()); + } + if (httpCookie.getPath() != null) { + cookie.setPath(httpCookie.getPath()); + } + if (httpCookie.getSameSite() != null) { + cookie.setAttribute("SameSite", httpCookie.getSameSite()); + } + cookie.setSecure(httpCookie.isSecure()); + cookie.setHttpOnly(httpCookie.isHttpOnly()); + this.response.addCookie(cookie); } } }