Skip to content

Commit c98e01a

Browse files
gregturnrstoyanchev
authored andcommitted
Create session-based WebSessionIdResolver
Introduces HeaderSessionIdResolver, which reads session ids from a session header and generates a session response header when new session are created. Related issue: SPR-15917
1 parent 03eb6f7 commit c98e01a

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2017 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+
* http://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+
package org.springframework.web.server.session;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
21+
import org.springframework.http.HttpHeaders;
22+
import org.springframework.util.Assert;
23+
import org.springframework.web.server.ServerWebExchange;
24+
25+
/**
26+
* Header-based {@link WebSessionIdResolver}.
27+
*
28+
* @author Greg Turnquist
29+
* @since 5.0
30+
*/
31+
public class HeaderSessionIdResolver implements WebSessionIdResolver {
32+
33+
private String headerName = "SESSION";
34+
35+
/**
36+
* Set the name of the session header to use for the session id.
37+
* <p>By default set to "SESSION".
38+
* @param headerName the header name
39+
*/
40+
public void setHeaderName(String headerName) {
41+
Assert.hasText(headerName, "'headerName' must not be empty.");
42+
this.headerName = headerName;
43+
}
44+
45+
/**
46+
* Return the configured header name.
47+
*/
48+
public String getHeaderName() {
49+
return this.headerName;
50+
}
51+
52+
@Override
53+
public List<String> resolveSessionIds(ServerWebExchange exchange) {
54+
HttpHeaders headers = exchange.getRequest().getHeaders();
55+
List<String> sessionHeaders = headers.get(this.getHeaderName());
56+
if (sessionHeaders == null) {
57+
return Collections.emptyList();
58+
}
59+
return sessionHeaders;
60+
}
61+
62+
@Override
63+
public void setSessionId(ServerWebExchange exchange, String id) {
64+
Assert.notNull(id, "'id' is required.");
65+
exchange.getResponse().getHeaders().set(this.headerName, id);
66+
}
67+
68+
@Override
69+
public void expireSession(ServerWebExchange exchange) {
70+
this.setSessionId(exchange, "");
71+
}
72+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2017 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+
* http://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+
package org.springframework.web.server.session;
17+
18+
import static org.hamcrest.collection.IsCollectionWithSize.*;
19+
import static org.hamcrest.core.Is.*;
20+
import static org.hamcrest.core.IsCollectionContaining.*;
21+
import static org.junit.Assert.*;
22+
23+
import java.time.Clock;
24+
import java.time.Duration;
25+
import java.time.Instant;
26+
import java.time.ZoneId;
27+
import java.util.UUID;
28+
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
import reactor.core.publisher.Mono;
32+
import org.springframework.http.codec.ServerCodecConfigurer;
33+
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
34+
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
35+
import org.springframework.web.server.ServerWebExchange;
36+
import org.springframework.web.server.WebSession;
37+
import org.springframework.web.server.adapter.DefaultServerWebExchange;
38+
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
39+
40+
/**
41+
* Tests using {@link HeaderSessionIdResolver}.
42+
*
43+
* @author Greg Turnquist
44+
*/
45+
public class HeaderSessionIdResolverTests {
46+
47+
private static final Clock CLOCK = Clock.system(ZoneId.of("GMT"));
48+
49+
private HeaderSessionIdResolver idResolver;
50+
51+
private DefaultWebSessionManager manager;
52+
53+
private ServerWebExchange exchange;
54+
55+
@Before
56+
public void setUp() {
57+
this.idResolver = new HeaderSessionIdResolver();
58+
this.manager = new DefaultWebSessionManager();
59+
this.manager.setSessionIdResolver(this.idResolver);
60+
61+
MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
62+
MockServerHttpResponse response = new MockServerHttpResponse();
63+
64+
this.exchange = new DefaultServerWebExchange(request, response, this.manager,
65+
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
66+
}
67+
68+
@Test
69+
public void getSessionWithoutStarting() throws Exception {
70+
WebSession session = this.manager.getSession(this.exchange).block();
71+
session.save();
72+
73+
assertFalse(session.isStarted());
74+
assertFalse(session.isExpired());
75+
assertNull(this.manager.getSessionStore().retrieveSession(session.getId()).block());
76+
}
77+
78+
@Test
79+
public void startSessionExplicitly() throws Exception {
80+
WebSession session = this.manager.getSession(this.exchange).block();
81+
session.start();
82+
session.save().block();
83+
84+
assertThat(this.exchange.getResponse().getHeaders().containsKey("SESSION"), is(true));
85+
assertThat(this.exchange.getResponse().getHeaders().get("SESSION"), hasSize(1));
86+
assertThat(this.exchange.getResponse().getHeaders().get("SESSION"), hasItem(session.getId()));
87+
}
88+
89+
@Test
90+
public void startSessionImplicitly() throws Exception {
91+
WebSession session = this.manager.getSession(this.exchange).block();
92+
session.getAttributes().put("foo", "bar");
93+
session.save();
94+
95+
assertNotNull(this.exchange.getResponse().getHeaders().get("SESSION"));
96+
}
97+
98+
@Test
99+
public void existingSession() throws Exception {
100+
UUID sessionId = UUID.randomUUID();
101+
DefaultWebSession existing = createDefaultWebSession(sessionId);
102+
this.manager.getSessionStore().storeSession(existing);
103+
104+
this.exchange = this.exchange.mutate()
105+
.request(this.exchange.getRequest().mutate()
106+
.header("SESSION", sessionId.toString())
107+
.build())
108+
.build();
109+
110+
WebSession actual = this.manager.getSession(this.exchange).block();
111+
assertNotNull(actual);
112+
assertEquals(existing.getId(), actual.getId());
113+
}
114+
115+
@Test
116+
public void existingSessionIsExpired() throws Exception {
117+
UUID sessionId = UUID.randomUUID();
118+
DefaultWebSession existing = createDefaultWebSession(sessionId);
119+
existing.start();
120+
Instant lastAccessTime = Instant.now(CLOCK).minus(Duration.ofMinutes(31));
121+
existing = new DefaultWebSession(existing, lastAccessTime, s -> Mono.empty());
122+
this.manager.getSessionStore().storeSession(existing);
123+
124+
this.exchange = this.exchange.mutate()
125+
.request(this.exchange.getRequest().mutate()
126+
.header("SESSION", sessionId.toString())
127+
.build())
128+
.build();
129+
130+
WebSession actual = this.manager.getSession(this.exchange).block();
131+
assertNotSame(existing, actual);
132+
}
133+
134+
@Test
135+
public void multipleSessionIds() throws Exception {
136+
UUID sessionId = UUID.randomUUID();
137+
DefaultWebSession existing = createDefaultWebSession(sessionId);
138+
this.manager.getSessionStore().storeSession(existing);
139+
this.manager.getSessionStore().storeSession(createDefaultWebSession(UUID.randomUUID()));
140+
this.manager.getSessionStore().storeSession(createDefaultWebSession(UUID.randomUUID()));
141+
142+
this.exchange = this.exchange.mutate()
143+
.request(this.exchange.getRequest().mutate()
144+
.header("SESSION", sessionId.toString())
145+
.build())
146+
.build();
147+
148+
WebSession actual = this.manager.getSession(this.exchange).block();
149+
assertNotNull(actual);
150+
assertEquals(existing.getId(), actual.getId());
151+
}
152+
153+
@Test
154+
public void alternateHeaderName() throws Exception {
155+
this.idResolver.setHeaderName("alternateHeaderName");
156+
157+
UUID sessionId = UUID.randomUUID();
158+
DefaultWebSession existing = createDefaultWebSession(sessionId);
159+
this.manager.getSessionStore().storeSession(existing);
160+
161+
this.exchange = this.exchange.mutate()
162+
.request(this.exchange.getRequest().mutate()
163+
.header("alternateHeaderName", sessionId.toString())
164+
.build())
165+
.build();
166+
167+
WebSession actual = this.manager.getSession(this.exchange).block();
168+
assertNotNull(actual);
169+
assertEquals(existing.getId(), actual.getId());
170+
}
171+
172+
private DefaultWebSession createDefaultWebSession(UUID sessionId) {
173+
return new DefaultWebSession(() -> sessionId, CLOCK, (s, session) -> Mono.empty(), s -> Mono.empty());
174+
}
175+
}

0 commit comments

Comments
 (0)