Skip to content

Commit 94e7fb8

Browse files
Merge branch '3.0.x'
2 parents 5885861 + f8c1fdb commit 94e7fb8

File tree

15 files changed

+642
-0
lines changed

15 files changed

+642
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
plugins {
2+
id "org.gretty" version "4.0.0"
3+
id "io.spring.convention.spring-sample-war"
4+
}
5+
6+
dependencies {
7+
implementation project(':spring-session-data-redis')
8+
implementation "io.lettuce:lettuce-core"
9+
implementation "org.springframework:spring-webmvc"
10+
implementation "org.springframework.security:spring-security-config"
11+
implementation "org.springframework.security:spring-security-web"
12+
implementation "com.fasterxml.jackson.core:jackson-databind"
13+
implementation "org.slf4j:slf4j-api"
14+
implementation "org.slf4j:jcl-over-slf4j"
15+
implementation "org.slf4j:log4j-over-slf4j"
16+
implementation "ch.qos.logback:logback-classic"
17+
implementation "org.testcontainers:testcontainers"
18+
19+
providedCompile "jakarta.servlet:jakarta.servlet-api:6.0.0"
20+
21+
testImplementation "org.springframework.security:spring-security-test"
22+
testImplementation "org.assertj:assertj-core"
23+
testImplementation "org.springframework:spring-test"
24+
testImplementation "org.junit.jupiter:junit-jupiter-api"
25+
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
26+
}
27+
28+
gretty {
29+
jvmArgs = ['-Dspring.profiles.active=embedded-redis']
30+
servletContainer = 'tomcat10'
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2014-2022 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 rest;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.ExtendWith;
22+
import org.testcontainers.containers.GenericContainer;
23+
import sample.SecurityConfig;
24+
import sample.mvc.MvcConfig;
25+
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
30+
import org.springframework.security.test.context.support.WithMockUser;
31+
import org.springframework.session.Session;
32+
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
33+
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
34+
import org.springframework.session.web.http.HttpSessionIdResolver;
35+
import org.springframework.session.web.http.SessionRepositoryFilter;
36+
import org.springframework.test.context.ContextConfiguration;
37+
import org.springframework.test.context.junit.jupiter.SpringExtension;
38+
import org.springframework.test.context.web.WebAppConfiguration;
39+
import org.springframework.test.web.servlet.MockMvc;
40+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
41+
import org.springframework.web.context.WebApplicationContext;
42+
43+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
44+
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
45+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
47+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
48+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
49+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
50+
51+
@ExtendWith(SpringExtension.class)
52+
@ContextConfiguration(classes = { RestMockMvcTests.Config.class, SecurityConfig.class, MvcConfig.class })
53+
@WebAppConfiguration
54+
class RestMockMvcTests {
55+
56+
private static final String DOCKER_IMAGE = "redis:7.0.4-alpine";
57+
58+
@Autowired
59+
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
60+
61+
@Autowired
62+
private WebApplicationContext context;
63+
64+
private MockMvc mvc;
65+
66+
@BeforeEach
67+
void setup() {
68+
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).alwaysDo(print())
69+
.addFilters(this.sessionRepositoryFilter).apply(springSecurity()).build();
70+
}
71+
72+
@Test
73+
void noSessionOnNoCredentials() throws Exception {
74+
this.mvc.perform(get("/")).andExpect(header().doesNotExist("X-Auth-Token"))
75+
.andExpect(status().isUnauthorized());
76+
}
77+
78+
@WithMockUser
79+
@Test
80+
void autheticatedAnnotation() throws Exception {
81+
this.mvc.perform(get("/")).andExpect(content().string("{\"username\":\"user\"}"));
82+
}
83+
84+
@Test
85+
void autheticatedRequestPostProcessor() throws Exception {
86+
this.mvc.perform(get("/").with(user("user"))).andExpect(content().string("{\"username\":\"user\"}"));
87+
}
88+
89+
@Configuration
90+
@EnableRedisHttpSession
91+
static class Config {
92+
93+
@Bean
94+
GenericContainer redisContainer() {
95+
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379);
96+
redisContainer.start();
97+
return redisContainer;
98+
}
99+
100+
@Bean
101+
LettuceConnectionFactory redisConnectionFactory() {
102+
return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort());
103+
}
104+
105+
@Bean
106+
HttpSessionIdResolver httpSessionIdResolver() {
107+
return HeaderHttpSessionIdResolver.xAuthToken();
108+
}
109+
110+
}
111+
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2014-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 sample;
18+
19+
import java.util.Base64;
20+
import java.util.Collections;
21+
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.http.HttpEntity;
26+
import org.springframework.http.HttpHeaders;
27+
import org.springframework.http.HttpMethod;
28+
import org.springframework.http.HttpStatus;
29+
import org.springframework.http.MediaType;
30+
import org.springframework.http.ResponseEntity;
31+
import org.springframework.web.client.HttpClientErrorException;
32+
import org.springframework.web.client.RestTemplate;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
36+
37+
/**
38+
* @author Pool Dolorier
39+
*/
40+
class RestTests {
41+
42+
private static final String AUTHORIZATION = "Authorization";
43+
44+
private static final String BASIC = "Basic ";
45+
46+
private static final String X_AUTH_TOKEN = "X-Auth-Token";
47+
48+
private RestTemplate restTemplate;
49+
50+
private String baseUrl;
51+
52+
@BeforeEach
53+
void setUp() {
54+
this.baseUrl = "http://localhost:" + System.getProperty("app.port");
55+
this.restTemplate = new RestTemplate();
56+
}
57+
58+
@Test
59+
void unauthenticatedUserSentToLogInPage() {
60+
HttpHeaders headers = new HttpHeaders();
61+
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
62+
assertThatExceptionOfType(HttpClientErrorException.class)
63+
.isThrownBy(() -> getForUser(this.baseUrl + "/", headers, String.class))
64+
.satisfies((e) -> assertThat(e.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED));
65+
}
66+
67+
@Test
68+
void authenticateWithBasicWorks() {
69+
String auth = getAuth("user", "password");
70+
HttpHeaders headers = getHttpHeaders();
71+
headers.set(AUTHORIZATION, BASIC + auth);
72+
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
73+
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
74+
assertThat(entity.getHeaders().containsKey(X_AUTH_TOKEN)).isTrue();
75+
assertThat(entity.getBody().getUsername()).isEqualTo("user");
76+
}
77+
78+
@Test
79+
void authenticateWithXAuthTokenWorks() {
80+
String auth = getAuth("user", "password");
81+
HttpHeaders headers = getHttpHeaders();
82+
headers.set(AUTHORIZATION, BASIC + auth);
83+
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
84+
85+
String token = entity.getHeaders().getFirst(X_AUTH_TOKEN);
86+
87+
HttpHeaders authTokenHeader = new HttpHeaders();
88+
authTokenHeader.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
89+
authTokenHeader.set(X_AUTH_TOKEN, token);
90+
ResponseEntity<User> authTokenResponse = getForUser(this.baseUrl + "/", authTokenHeader, User.class);
91+
assertThat(authTokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
92+
assertThat(authTokenResponse.getBody().getUsername()).isEqualTo("user");
93+
}
94+
95+
@Test
96+
void logout() {
97+
String auth = getAuth("user", "password");
98+
HttpHeaders headers = getHttpHeaders();
99+
headers.set(AUTHORIZATION, BASIC + auth);
100+
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
101+
102+
String token = entity.getHeaders().getFirst(X_AUTH_TOKEN);
103+
104+
HttpHeaders logoutHeader = getHttpHeaders();
105+
logoutHeader.set(X_AUTH_TOKEN, token);
106+
ResponseEntity<User> logoutResponse = getForUser(this.baseUrl + "/logout", logoutHeader, User.class);
107+
assertThat(logoutResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
108+
}
109+
110+
private <T> ResponseEntity<T> getForUser(String resourceUrl, HttpHeaders headers, Class<T> type) {
111+
return this.restTemplate.exchange(resourceUrl, HttpMethod.GET, new HttpEntity<T>(headers), type);
112+
}
113+
114+
private HttpHeaders getHttpHeaders() {
115+
HttpHeaders headers = new HttpHeaders();
116+
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
117+
return headers;
118+
}
119+
120+
private String getAuth(String user, String password) {
121+
String auth = user + ":" + password;
122+
return Base64.getEncoder().encodeToString(auth.getBytes());
123+
}
124+
125+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2014-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 sample;
18+
19+
/**
20+
* @author Pool Dolorier
21+
*/
22+
public class User {
23+
24+
private String username;
25+
26+
public String getUsername() {
27+
return this.username;
28+
}
29+
30+
public void setUsername(String username) {
31+
this.username = username;
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2014-2022 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 sample;
18+
19+
import org.testcontainers.containers.GenericContainer;
20+
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Primary;
24+
import org.springframework.context.annotation.Profile;
25+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
26+
27+
@Configuration
28+
@Profile("embedded-redis")
29+
public class EmbeddedRedisConfig {
30+
31+
private static final String DOCKER_IMAGE = "redis:7.0.4-alpine";
32+
33+
@Bean
34+
public GenericContainer redisContainer() {
35+
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379);
36+
redisContainer.start();
37+
return redisContainer;
38+
}
39+
40+
@Bean
41+
@Primary
42+
public LettuceConnectionFactory redisConnectionFactory() {
43+
return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort());
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2014-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 sample;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.context.annotation.Import;
22+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
23+
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
24+
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
25+
import org.springframework.session.web.http.HttpSessionIdResolver;
26+
27+
@Import(EmbeddedRedisConfig.class)
28+
// tag::class[]
29+
@Configuration
30+
@EnableRedisHttpSession // <1>
31+
public class HttpSessionConfig {
32+
33+
@Bean
34+
public LettuceConnectionFactory connectionFactory() {
35+
return new LettuceConnectionFactory(); // <2>
36+
}
37+
38+
@Bean
39+
public HttpSessionIdResolver httpSessionIdResolver() {
40+
return HeaderHttpSessionIdResolver.xAuthToken(); // <3>
41+
}
42+
43+
}
44+
// end::class[]

0 commit comments

Comments
 (0)