Skip to content

Commit a6e36d5

Browse files
committed
Cache request URI in cookie instead of session
Fixes: spring-projectsgh-7157
1 parent cd0bec4 commit a6e36d5

File tree

9 files changed

+382
-14
lines changed

9 files changed

+382
-14
lines changed

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
import org.springframework.security.web.server.savedrequest.NoOpServerRequestCache;
153153
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
154154
import org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter;
155-
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
155+
import org.springframework.security.web.server.savedrequest.CookieServerRequestCache;
156156
import org.springframework.security.web.server.transport.HttpsRedirectWebFilter;
157157
import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter;
158158
import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
@@ -2824,7 +2824,7 @@ private ExceptionHandlingSpec() {}
28242824
* @see #requestCache()
28252825
*/
28262826
public class RequestCacheSpec {
2827-
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
2827+
private ServerRequestCache requestCache = new CookieServerRequestCache();
28282828

28292829
/**
28302830
* Configures the cache used

config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ public void defaults() {
109109
.expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+")
110110
.returnResult(String.class);
111111

112-
assertThat(result.getResponseCookies()).isEmpty();
112+
assertThat(result.getResponseCookies()).hasSize(1);
113+
assertThat(result.getResponseCookies().getFirst("REDIRECT_URI")).isNotNull();
113114
// there is no need to try and load the SecurityContext by default
114115
securityContext.assertWasNotSubscribed();
115116
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationRequestRedirectWebFilter.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
2525
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
2626
import org.springframework.security.web.server.ServerRedirectStrategy;
27+
import org.springframework.security.web.server.savedrequest.CookieServerRequestCache;
2728
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
28-
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
2929
import org.springframework.util.Assert;
3030
import org.springframework.web.server.ServerWebExchange;
3131
import org.springframework.web.server.WebFilter;
@@ -69,7 +69,7 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
6969
private final ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
7070
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
7171
new WebSessionOAuth2ServerAuthorizationRequestRepository();
72-
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
72+
private ServerRequestCache requestCache = new CookieServerRequestCache();
7373

7474
/**
7575
* Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.

web/src/main/java/org/springframework/security/web/server/authentication/RedirectServerAuthenticationEntryPoint.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
2020

2121
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
2222
import org.springframework.security.web.server.ServerRedirectStrategy;
23+
import org.springframework.security.web.server.savedrequest.CookieServerRequestCache;
2324
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
24-
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
2525
import reactor.core.publisher.Mono;
2626

2727
import org.springframework.security.core.AuthenticationException;
@@ -41,7 +41,7 @@ public class RedirectServerAuthenticationEntryPoint
4141

4242
private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
4343

44-
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
44+
private ServerRequestCache requestCache = new CookieServerRequestCache();
4545

4646
/**
4747
* Creates an instance

web/src/main/java/org/springframework/security/web/server/authentication/RedirectServerAuthenticationSuccessHandler.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
2020
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
2121
import org.springframework.security.web.server.ServerRedirectStrategy;
2222
import org.springframework.security.web.server.WebFilterExchange;
23+
import org.springframework.security.web.server.savedrequest.CookieServerRequestCache;
2324
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
24-
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
2525
import org.springframework.util.Assert;
2626
import org.springframework.web.server.ServerWebExchange;
2727
import reactor.core.publisher.Mono;
@@ -40,7 +40,7 @@ public class RedirectServerAuthenticationSuccessHandler
4040

4141
private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
4242

43-
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
43+
private ServerRequestCache requestCache = new CookieServerRequestCache();
4444

4545
/**
4646
* Creates a new instance with location of "/"
@@ -57,7 +57,7 @@ public RedirectServerAuthenticationSuccessHandler(String location) {
5757
}
5858

5959
/**
60-
* Sets the {@link ServerRequestCache} used to redirect to. Default is {@link WebSessionServerRequestCache}.
60+
* Sets the {@link ServerRequestCache} used to redirect to. Default is {@link CookieServerRequestCache}.
6161
* @param requestCache the cache to use
6262
*/
6363
public void setRequestCache(ServerRequestCache requestCache) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.server.savedrequest;
18+
19+
import org.springframework.http.HttpCookie;
20+
import org.springframework.http.HttpMethod;
21+
import org.springframework.http.MediaType;
22+
import org.springframework.http.ResponseCookie;
23+
import org.springframework.http.server.reactive.ServerHttpRequest;
24+
import org.springframework.http.server.reactive.ServerHttpResponse;
25+
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
26+
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
27+
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
28+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
29+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.MultiValueMap;
32+
import org.springframework.web.server.ServerWebExchange;
33+
import reactor.core.publisher.Mono;
34+
35+
import java.net.URI;
36+
import java.time.Duration;
37+
import java.util.Base64;
38+
import java.util.Collections;
39+
40+
/**
41+
* An implementation of {@link ServerRequestCache} that saves the
42+
* requested URI in a cookie.
43+
*
44+
* @author Eleftheria Stein
45+
* @since 5.3
46+
*/
47+
public class CookieServerRequestCache implements ServerRequestCache {
48+
private static final String REDIRECT_URI_COOKIE_NAME = "REDIRECT_URI";
49+
50+
private static final Duration COOKIE_MAX_AGE = Duration.ofSeconds(-1);
51+
52+
private ServerWebExchangeMatcher saveRequestMatcher = createDefaultRequestMatcher();
53+
54+
/**
55+
* Sets the matcher to determine if the request should be saved. The default is to match
56+
* on any GET request.
57+
*
58+
* @param saveRequestMatcher the {@link ServerWebExchangeMatcher} that determines if
59+
* the request should be saved
60+
*/
61+
public void setSaveRequestMatcher(ServerWebExchangeMatcher saveRequestMatcher) {
62+
Assert.notNull(saveRequestMatcher, "saveRequestMatcher cannot be null");
63+
this.saveRequestMatcher = saveRequestMatcher;
64+
}
65+
66+
@Override
67+
public Mono<Void> saveRequest(ServerWebExchange exchange) {
68+
return this.saveRequestMatcher.matches(exchange)
69+
.filter(m -> m.isMatch())
70+
.map(m -> exchange.getResponse())
71+
.map(ServerHttpResponse::getCookies)
72+
.doOnNext(cookies -> cookies.add(REDIRECT_URI_COOKIE_NAME, createRedirectUriCookie(exchange.getRequest())))
73+
.then();
74+
}
75+
76+
@Override
77+
public Mono<URI> getRedirectUri(ServerWebExchange exchange) {
78+
MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();
79+
return Mono.justOrEmpty(cookieMap.getFirst(REDIRECT_URI_COOKIE_NAME))
80+
.map(HttpCookie::getValue)
81+
.map(CookieServerRequestCache::decodeCookie)
82+
.onErrorResume(IllegalArgumentException.class, e -> Mono.empty())
83+
.map(URI::create);
84+
}
85+
86+
@Override
87+
public Mono<ServerHttpRequest> removeMatchingRequest(ServerWebExchange exchange) {
88+
return Mono.just(exchange.getResponse())
89+
.map(ServerHttpResponse::getCookies)
90+
.doOnNext(cookies -> cookies.add(REDIRECT_URI_COOKIE_NAME, invalidateRedirectUriCookie(exchange.getRequest())))
91+
.thenReturn(exchange.getRequest());
92+
}
93+
94+
private static ResponseCookie createRedirectUriCookie(ServerHttpRequest request) {
95+
String path = request.getPath().pathWithinApplication().value();
96+
String query = request.getURI().getRawQuery();
97+
String redirectUri = path + (query != null ? "?" + query : "");
98+
99+
return createResponseCookie(request, encodeCookie(redirectUri), COOKIE_MAX_AGE);
100+
}
101+
102+
private static ResponseCookie invalidateRedirectUriCookie(ServerHttpRequest request) {
103+
return createResponseCookie(request, null, Duration.ZERO);
104+
}
105+
106+
private static ResponseCookie createResponseCookie(ServerHttpRequest request, String cookieValue, Duration age) {
107+
return ResponseCookie.from(REDIRECT_URI_COOKIE_NAME, cookieValue)
108+
.path(request.getPath().contextPath().value() + "/")
109+
.maxAge(age)
110+
.httpOnly(true)
111+
.secure("https".equalsIgnoreCase(request.getURI().getScheme()))
112+
.sameSite("Lax")
113+
.build();
114+
}
115+
116+
private static String encodeCookie(String cookieValue) {
117+
return new String(Base64.getEncoder().encode(cookieValue.getBytes()));
118+
}
119+
120+
private static String decodeCookie(String encodedCookieValue) {
121+
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
122+
}
123+
124+
private static ServerWebExchangeMatcher createDefaultRequestMatcher() {
125+
ServerWebExchangeMatcher get = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**");
126+
ServerWebExchangeMatcher notFavicon = new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers("/favicon.*"));
127+
MediaTypeServerWebExchangeMatcher html = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
128+
html.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
129+
return new AndServerWebExchangeMatcher(get, notFavicon, html);
130+
}
131+
}

web/src/main/java/org/springframework/security/web/server/savedrequest/ServerRequestCacheWebFilter.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
2929
* @since 5.0
3030
*/
3131
public class ServerRequestCacheWebFilter implements WebFilter {
32-
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
32+
private ServerRequestCache requestCache = new CookieServerRequestCache();
3333

3434
@Override
3535
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

0 commit comments

Comments
 (0)