Compare commits

..

16 Commits

Author SHA1 Message Date
Rob Winch
a0d03adbe1 Release 3.0.0-RC2 2022-11-09 12:44:49 -06:00
Rob Winch
b229103d8c Remove spring-session-sample-javaconfig-rest
Works around a compatability issue for now.

Issue gh-2201
2022-11-09 12:44:31 -06:00
Rob Winch
94b441c676 Define websocket depenendencies
Issue gh-2204
2022-11-09 12:43:22 -06:00
Rob Winch
b2f10c6752 Next Development Version 2022-11-09 10:24:54 -06:00
Rob Winch
b3d228eb2e Release 3.0.0-RC2 2022-11-09 10:24:22 -06:00
Rob Winch
19dd3d8be1 Revert "Update for docs-build branch"
This reverts commit cd628fe5af.
2022-11-09 10:22:52 -06:00
Rob Winch
1aaffb28fc Update to Spring Security 6.0.0-RC2
Closes gh-2206
2022-11-09 09:56:24 -06:00
Rob Winch
75af61ca6c Update to Spring Data 2022.0.0-RC2
Closes gh-2205
2022-11-09 09:55:30 -06:00
Rob Winch
2fff593423 Update to Spring Framework 6.0.0-RC4
Closes gh-2204
2022-11-09 09:54:10 -06:00
Rob Winch
6a381d3226 Update to reactor-bom:2022.0.0
Closes gh-2203
2022-11-09 09:53:09 -06:00
Rob Winch
cd628fe5af Update for docs-build branch 2022-11-08 12:59:06 -06:00
Andy Wilkinson
ee4df64bb1 Align wth Servlet 6.0 API 2022-11-07 10:27:13 -06:00
Andy Wilkinson
d850762bce Avoid changing line endings of png and mmdb files 2022-11-07 10:05:58 -06:00
Vedran Pavic
f71d1d6ca4 Add Spring Session BOM module
With Spring Session Data Geode module being removed from the BOM, all of Spring Session's modules are now managed by this repository.

This means that the BOM itself can be moved to this repository, in order to simplify the overall project maintenance.

See gh-2195
2022-10-25 14:33:58 -05:00
Vedran Pavic
e5eeacec5f Update security config in samples
This commit updates security configuration in samples to:

- use AuthorizationFilter instead of FilterSecurityInterceptor
- update session creation policy in REST sample
2022-10-25 14:27:39 -05:00
Rob Winch
62ec64310b Next Development Version 2022-10-18 20:46:04 -05:00
35 changed files with 41 additions and 891 deletions

4
.gitattributes vendored
View File

@@ -3,3 +3,7 @@
*.bat text eol=crlf
*.jar binary
*.png binary
*.mmdb binary

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
version=3.0.0-RC1
version=3.0.0-RC2

View File

@@ -1,12 +1,12 @@
dependencyManagement {
imports {
mavenBom 'io.projectreactor:reactor-bom:2022.0.0-RC1'
mavenBom 'io.projectreactor:reactor-bom:2022.0.0'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.4.20221013'
mavenBom 'org.junit:junit-bom:5.9.1'
mavenBom 'org.mockito:mockito-bom:4.8.1'
mavenBom 'org.springframework:spring-framework-bom:6.0.0-RC1'
mavenBom 'org.springframework.data:spring-data-bom:2022.0.0-RC1'
mavenBom 'org.springframework.security:spring-security-bom:6.0.0-RC1'
mavenBom 'org.springframework:spring-framework-bom:6.0.0-RC4'
mavenBom 'org.springframework.data:spring-data-bom:2022.0.0-RC2'
mavenBom 'org.springframework.security:spring-security-bom:6.0.0-RC2'
mavenBom 'org.testcontainers:testcontainers-bom:1.17.3'
}
@@ -22,7 +22,9 @@ dependencyManagement {
dependency 'com.zaxxer:HikariCP:5.0.1'
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
dependency 'io.lettuce:lettuce-core:6.2.1.RELEASE'
dependency 'jakarta.servlet:jakarta.servlet-api:5.0.0'
dependency 'jakarta.servlet:jakarta.servlet-api:6.0.0'
dependency 'jakarta.websocket:jakarta.websocket-api:2.1.0'
dependency 'jakarta.websocket:jakarta.websocket-client-api:2.1.0'
dependency 'mysql:mysql-connector-java:8.0.30'
dependencySet(group: 'org.apache.derby', version: '10.16.1.1') {
entry 'derby'

View File

@@ -12,6 +12,7 @@ plugins {
rootProject.name = 'spring-session-build'
include 'spring-session-bom'
include 'spring-session-core'
include 'spring-session-data-mongodb'
include 'spring-session-data-redis'

View File

@@ -0,0 +1,15 @@
import io.spring.gradle.convention.SpringModulePlugin
plugins {
id("io.spring.convention.bom")
}
dependencies {
constraints {
project.rootProject.allprojects { project ->
project.plugins.withType(SpringModulePlugin) {
api(project)
}
}
}
}

View File

@@ -98,6 +98,7 @@ public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
System.out.println(headerValue);
return (headerValue != null) ? Collections.singletonList(headerValue) : Collections.emptyList();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 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,14 +19,11 @@ package org.springframework.session.web.http;
import java.time.Duration;
import java.util.Collections;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Set;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -41,7 +38,6 @@ import org.springframework.session.Session;
* @author Vedran Pavic
* @since 1.1
*/
@SuppressWarnings("deprecation")
class HttpSessionAdapter<S extends Session> implements HttpSession {
private static final Log logger = LogFactory.getLog(HttpSessionAdapter.class);
@@ -101,35 +97,18 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
return (int) this.session.getMaxInactiveInterval().getSeconds();
}
@Override
public HttpSessionContext getSessionContext() {
return NOOP_SESSION_CONTEXT;
}
@Override
public Object getAttribute(String name) {
checkState();
return this.session.getAttribute(name);
}
@Override
public Object getValue(String name) {
return getAttribute(name);
}
@Override
public Enumeration<String> getAttributeNames() {
checkState();
return Collections.enumeration(this.session.getAttributeNames());
}
@Override
public String[] getValueNames() {
checkState();
Set<String> attrs = this.session.getAttributeNames();
return attrs.toArray(new String[0]);
}
@Override
public void setAttribute(String name, Object value) {
checkState();
@@ -156,11 +135,6 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
}
}
@Override
public void putValue(String name, Object value) {
setAttribute(name, value);
}
@Override
public void removeAttribute(String name) {
checkState();
@@ -176,11 +150,6 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
}
}
@Override
public void removeValue(String name) {
removeAttribute(name);
}
@Override
public void invalidate() {
checkState();
@@ -203,32 +172,4 @@ class HttpSessionAdapter<S extends Session> implements HttpSession {
}
}
private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() {
@Override
public HttpSession getSession(String sessionId) {
return null;
}
@Override
public Enumeration<String> getIds() {
return EMPTY_ENUMERATION;
}
};
private static final Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
throw new NoSuchElementException("a");
}
};
}

View File

@@ -24,7 +24,6 @@ import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -40,7 +39,6 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionContext;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.BeforeEach;
@@ -316,52 +314,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterValue() throws Exception {
final String ATTR = "ATTR";
final String VALUE = "VALUE";
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
wrappedRequest.getSession().putValue(ATTR, VALUE);
assertThat(wrappedRequest.getSession().getValue(ATTR)).isEqualTo(VALUE);
assertThat(Arrays.asList(wrappedRequest.getSession().getValueNames())).containsOnly(ATTR);
}
});
nextRequest();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
assertThat(wrappedRequest.getSession().getValue(ATTR)).isEqualTo(VALUE);
assertThat(Arrays.asList(wrappedRequest.getSession().getValueNames())).containsOnly(ATTR);
}
});
nextRequest();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
assertThat(wrappedRequest.getSession().getValue(ATTR)).isEqualTo(VALUE);
wrappedRequest.getSession().removeValue(ATTR);
assertThat(wrappedRequest.getSession().getValue(ATTR)).isNull();
}
});
nextRequest();
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
assertThat(wrappedRequest.getSession().getValue(ATTR)).isNull();
}
});
}
@Test
void doFilterIsNewTrue() throws Exception {
doFilter(new DoInFilter() {
@@ -637,27 +589,6 @@ class SessionRepositoryFilterTests {
assertThat(session.getSecure()).describedAs("Session Cookie should be marked as Secure").isTrue();
}
@Test
void doFilterSessionContext() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSessionContext sessionContext = wrappedRequest.getSession().getSessionContext();
assertThat(sessionContext).isNotNull();
assertThat(sessionContext.getSession("a")).isNull();
assertThat(sessionContext.getIds()).isNotNull();
assertThat(sessionContext.getIds().hasMoreElements()).isFalse();
try {
sessionContext.getIds().nextElement();
fail("Expected Exception");
}
catch (NoSuchElementException ignored) {
}
}
});
}
// --- saving
@Test
@@ -741,23 +672,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterInvalidateValueIllegalState() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.invalidate();
try {
session.getValue("attr");
fail("Expected Exception");
}
catch (IllegalStateException ignored) {
}
}
});
}
@Test
void doFilterInvalidateAttributeNamesIllegalState() throws Exception {
doFilter(new DoInFilter() {
@@ -775,23 +689,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterInvalidateValueNamesIllegalState() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.invalidate();
try {
session.getValueNames();
fail("Expected Exception");
}
catch (IllegalStateException ignored) {
}
}
});
}
@Test
void doFilterInvalidateSetAttributeIllegalState() throws Exception {
doFilter(new DoInFilter() {
@@ -809,23 +706,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterInvalidatePutValueIllegalState() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.invalidate();
try {
session.putValue("a", "b");
fail("Expected Exception");
}
catch (IllegalStateException ignored) {
}
}
});
}
@Test
void doFilterInvalidateRemoveAttributeIllegalState() throws Exception {
doFilter(new DoInFilter() {
@@ -843,23 +723,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterInvalidateRemoveValueIllegalState() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.invalidate();
try {
session.removeValue("name");
fail("Expected Exception");
}
catch (IllegalStateException ignored) {
}
}
});
}
@Test
void doFilterInvalidateNewIllegalState() throws Exception {
doFilter(new DoInFilter() {
@@ -921,20 +784,6 @@ class SessionRepositoryFilterTests {
});
}
@Test
void doFilterInvalidateSessionContext() throws Exception {
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
HttpSession session = wrappedRequest.getSession();
session.invalidate();
// no exception
session.getSessionContext();
}
});
}
@Test
void doFilterInvalidateMaxInteractiveInterval() throws Exception {
doFilter(new DoInFilter() {

View File

@@ -18,6 +18,8 @@ dependencies {
optional "org.mongodb:mongodb-driver-core"
testImplementation "org.mongodb:mongodb-driver-sync"
testImplementation "org.mongodb:mongodb-driver-reactivestreams"
testImplementation 'jakarta.websocket:jakarta.websocket-api'
testImplementation 'jakarta.websocket:jakarta.websocket-client-api'
integrationTestCompile "org.testcontainers:mongodb"
// Everything else

View File

@@ -1,4 +1,4 @@
name: ROOT
version: '3.0.0-RC1'
version: '3.0.0-RC2'
prerelease: 'true'
display_version: '3.0.0-RC1'
display_version: '3.0.0-RC2'

View File

@@ -51,7 +51,7 @@ public class RememberMeSecurityConfiguration {
return http
.formLogin(Customizer.withDefaults())
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
).build();
}

View File

@@ -15,7 +15,6 @@
*** xref:guides/boot-webflux-custom-cookie.adoc[Custom Cookie]
** Java Configuration
** XML Configuration
* xref:modules.adoc[Modules]
* xref:http-session.adoc[HttpSession Integration]
* xref:web-socket.adoc[WebSocket Integration]
* xref:web-session.adoc[WebSession Integration]

View File

@@ -1,22 +0,0 @@
[[modules]]
= Spring Session Modules
In Spring Session 1.x, all of the Spring Session's `SessionRepository` implementations were available within the `spring-session` artifact.
While convenient, this approach was not sustainable long-term as more features and `SessionRepository` implementations were added to the project.
With Spring Session 2.0, several modules were split off to be separate modules as well as managed repositories.
Spring Session for MongoDB was retired, but was later reactivated as a separate module.
As of Spring Session 2.6, Spring Session for MongoDB was merged back into Spring Session.
Now the situation with the various repositories and modules is as follows:
* https://github.com/spring-projects/spring-session[`spring-session` repository]
** Hosts the Spring Session Core, Spring Session for MongoDB, Spring Session for Redis, Spring Session JDBC, and Spring Session Hazelcast modules.
* https://github.com/spring-projects/spring-session-data-geode[`spring-session-data-geode` repository]
** Hosts the Spring Session Data Geode modules. Spring Session Data Geode has its own user guide, which you can find at the [https://spring.io/projects/spring-session-data-geode#learn site].
Finally, Spring Session also provides a Maven BOM ("`bill of materials`") module in order to help users with version management concerns:
* https://github.com/spring-projects/spring-session-bom[`spring-session-bom` repository]
** Hosts the Spring Session BOM module

View File

@@ -1,4 +1 @@
= What's New
Check also the Spring Session BOM https://github.com/spring-projects/spring-session-bom/wiki#release-notes[release notes]
for a list of new and noteworthy features, as well as upgrade instructions for each release.

View File

@@ -36,7 +36,7 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -35,7 +35,7 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -45,7 +45,7 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -35,7 +35,7 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -36,7 +36,7 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -54,7 +54,7 @@ public class WebSecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)

View File

@@ -1,31 +0,0 @@
plugins {
id "org.gretty" version "4.0.0"
id "io.spring.convention.spring-sample-war"
}
dependencies {
implementation project(':spring-session-data-redis')
implementation "io.lettuce:lettuce-core"
implementation "org.springframework:spring-webmvc"
implementation "org.springframework.security:spring-security-config"
implementation "org.springframework.security:spring-security-web"
implementation "com.fasterxml.jackson.core:jackson-databind"
implementation "org.slf4j:slf4j-api"
implementation "org.slf4j:jcl-over-slf4j"
implementation "org.slf4j:log4j-over-slf4j"
implementation "ch.qos.logback:logback-classic"
implementation "org.testcontainers:testcontainers"
providedCompile "jakarta.servlet:jakarta.servlet-api"
testImplementation "org.springframework.security:spring-security-test"
testImplementation "org.assertj:assertj-core"
testImplementation "org.springframework:spring-test"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
}
gretty {
jvmArgs = ['-Dspring.profiles.active=embedded-redis']
servletContainer = 'tomcat10'
}

View File

@@ -1,112 +0,0 @@
/*
* Copyright 2014-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.GenericContainer;
import sample.SecurityConfig;
import sample.mvc.MvcConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.session.Session;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { RestMockMvcTests.Config.class, SecurityConfig.class, MvcConfig.class })
@WebAppConfiguration
class RestMockMvcTests {
private static final String DOCKER_IMAGE = "redis:7.0.4-alpine";
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@BeforeEach
void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).alwaysDo(print())
.addFilters(this.sessionRepositoryFilter).apply(springSecurity()).build();
}
@Test
void noSessionOnNoCredentials() throws Exception {
this.mvc.perform(get("/")).andExpect(header().doesNotExist("X-Auth-Token"))
.andExpect(status().isUnauthorized());
}
@WithMockUser
@Test
void autheticatedAnnotation() throws Exception {
this.mvc.perform(get("/")).andExpect(content().string("{\"username\":\"user\"}"));
}
@Test
void autheticatedRequestPostProcessor() throws Exception {
this.mvc.perform(get("/").with(user("user"))).andExpect(content().string("{\"username\":\"user\"}"));
}
@Configuration
@EnableRedisHttpSession
static class Config {
@Bean
GenericContainer redisContainer() {
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379);
redisContainer.start();
return redisContainer;
}
@Bean
LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort());
}
@Bean
HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.util.Base64;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Pool Dolorier
*/
class RestTests {
private static final String AUTHORIZATION = "Authorization";
private static final String BASIC = "Basic ";
private static final String X_AUTH_TOKEN = "X-Auth-Token";
private RestTemplate restTemplate;
private String baseUrl;
@BeforeEach
void setUp() {
this.baseUrl = "http://localhost:" + System.getProperty("app.port");
this.restTemplate = new RestTemplate();
}
@Test
void unauthenticatedUserSentToLogInPage() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
assertThatExceptionOfType(HttpClientErrorException.class)
.isThrownBy(() -> getForUser(this.baseUrl + "/", headers, String.class))
.satisfies((e) -> assertThat(e.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED));
}
@Test
void authenticateWithBasicWorks() {
String auth = getAuth("user", "password");
HttpHeaders headers = getHttpHeaders();
headers.set(AUTHORIZATION, BASIC + auth);
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders().containsKey(X_AUTH_TOKEN)).isTrue();
assertThat(entity.getBody().getUsername()).isEqualTo("user");
}
@Test
void authenticateWithXAuthTokenWorks() {
String auth = getAuth("user", "password");
HttpHeaders headers = getHttpHeaders();
headers.set(AUTHORIZATION, BASIC + auth);
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
String token = entity.getHeaders().getFirst(X_AUTH_TOKEN);
HttpHeaders authTokenHeader = new HttpHeaders();
authTokenHeader.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
authTokenHeader.set(X_AUTH_TOKEN, token);
ResponseEntity<User> authTokenResponse = getForUser(this.baseUrl + "/", authTokenHeader, User.class);
assertThat(authTokenResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(authTokenResponse.getBody().getUsername()).isEqualTo("user");
}
@Test
void logout() {
String auth = getAuth("user", "password");
HttpHeaders headers = getHttpHeaders();
headers.set(AUTHORIZATION, BASIC + auth);
ResponseEntity<User> entity = getForUser(this.baseUrl + "/", headers, User.class);
String token = entity.getHeaders().getFirst(X_AUTH_TOKEN);
HttpHeaders logoutHeader = getHttpHeaders();
logoutHeader.set(X_AUTH_TOKEN, token);
ResponseEntity<User> logoutResponse = getForUser(this.baseUrl + "/logout", logoutHeader, User.class);
assertThat(logoutResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
}
private <T> ResponseEntity<T> getForUser(String resourceUrl, HttpHeaders headers, Class<T> type) {
return this.restTemplate.exchange(resourceUrl, HttpMethod.GET, new HttpEntity<T>(headers), type);
}
private HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
return headers;
}
private String getAuth(String user, String password) {
String auth = user + ":" + password;
return Base64.getEncoder().encodeToString(auth.getBytes());
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
/**
* @author Pool Dolorier
*/
public class User {
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2014-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.testcontainers.containers.GenericContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String DOCKER_IMAGE = "redis:7.0.4-alpine";
@Bean
public GenericContainer redisContainer() {
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379);
redisContainer.start();
return redisContainer;
}
@Bean
@Primary
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort());
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
@Import(EmbeddedRedisConfig.class)
// tag::class[]
@Configuration
@EnableRedisHttpSession // <1>
public class HttpSessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(); // <2>
}
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken(); // <3>
}
}
// end::class[]

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2014-2016 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
// tag::class[]
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
// end::class[]

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2014-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.savedrequest.NullRequestCache;
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfig {
// @formatter:off
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.requestCache((requestCache) -> requestCache
.requestCache(new NullRequestCache())
)
.httpBasic(Customizer.withDefaults())
.build();
}
// @formatter:on
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(User.withUsername("user").password("{noop}password").roles("USER").build());
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2014-2016 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* @author Rob Winch
*/
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.mvc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Rob Winch
*/
@Configuration
@EnableWebMvc
@ComponentScan
public class MvcConfig {
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.mvc;
import sample.HttpSessionConfig;
import sample.SecurityConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @author Rob Winch
*/
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// tag::config[]
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}
// end::config[]
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MvcConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2014-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.mvc;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Rob Winch
*/
@RestController
public class RestDemoController {
@RequestMapping(value = "/", produces = "application/json")
public Map<String, String> helloUser(Principal principal) {
HashMap<String, String> result = new HashMap<>();
result.put("username", principal.getName());
return result;
}
@RequestMapping("/logout")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void logout(HttpSession session) {
session.invalidate();
}
}

View File

@@ -1,14 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <logger name="org.springframework.security" level="DEBUG"/> -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>