Remove Mongo

Fixes gh-768
This commit is contained in:
Rob Winch
2017-04-26 19:47:25 -05:00
parent 02da23a2a0
commit 63b836b212
44 changed files with 1 additions and 3165 deletions

View File

@@ -3,7 +3,6 @@ apply plugin: 'io.spring.convention.spring-test'
dependencies {
testCompile project(':spring-session')
testCompile project(':spring-session-data-mongo')
testCompile project(':spring-session-data-redis')
testCompile "org.springframework:spring-jdbc"
testCompile "org.springframework:spring-messaging"

View File

@@ -1,168 +0,0 @@
= Spring Session - Mongo Repositories
Jakub Kubrynski
:toc:
This guide describes how to use Spring Session backed by Mongo.
NOTE: The completed guide can be found in the <<mongo-sample, mongo sample application>>.
== Updating Dependencies
Before you use Spring Session, you must ensure to update your dependencies.
We assume you are working with a working Spring Boot web application.
If you are using Maven, ensure to add the following dependencies:
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>{spring-session-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
----
ifeval::["{version-snapshot}" == "true"]
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
Ensure you have the following in your pom.xml:
.pom.xml
[source,xml]
----
<repositories>
<!-- ... -->
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
----
endif::[]
ifeval::["{version-milestone}" == "true"]
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
Ensure you have the following in your pom.xml:
.pom.xml
[source,xml]
----
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
----
endif::[]
[[mongo-spring-configuration]]
== Spring Configuration
After adding the required dependencies, we can create our Spring configuration.
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
// tag::config[]
All you have to do is to add the following Spring Configuration:
[source,java]
----
include::{samples-dir}boot/mongo/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
----
<1> The `@EnableMongoHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
In this instance Spring Session is backed by Mongo.
<2> We explicitly configure `JdkMongoSessionConverter` since Spring Security's objects cannot be automatically persisted using Jackson (the default if Jackson is on the classpath).
// end::config[]
[[boot-mongo-configuration]]
== Configuring the Mongo Connection
Spring Boot automatically creates a `MongoClient` that connects Spring Session to a Mongo Server on localhost on port 27017 (default port).
In a production environment you need to ensure to update your configuration to point to your Mongo server.
For example, you can include the following in your *application.properties*
.src/main/resources/application.properties
----
spring.data.mongodb.host=mongo-srv
spring.data.mongodb.port=27018
spring.data.mongodb.database=prod
----
For more information, refer to http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-mongodb[Connecting to MongoDB] portion of the Spring Boot documentation.
[[boot-servlet-configuration]]
== Servlet Container Initialization
Our <<boot-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
Fortunately, Spring Boot takes care of both of these steps for us.
[[mongo-sample]]
== Mongo Sample Application
The Mongo Sample Application demonstrates how to use Spring Session to transparently leverage Mongo to back a web application's `HttpSession` when using Spring Boot.
[[mongo-running]]
=== Running the Mongo Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
----
$ ./gradlew :samples:mongo:bootRun
----
You should now be able to access the application at http://localhost:8080/
[[boot-explore]]
=== Exploring the security Sample Application
Try using the application. Enter the following to log in:
* **Username** _user_
* **Password** _password_
Now click the **Login** button.
You should now see a message indicating your are logged in with the user entered previously.
The user's information is stored in Mongo rather than Tomcat's `HttpSession` implementation.
[[mongo-how]]
=== How does it work?
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Mongo.
Spring Session replaces the `HttpSession` with an implementation that is backed by Mongo.
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Mongo.
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
If you like, you can easily inspect the session using mongo client. For example, on a Linux based system you can type:
[NOTE]
====
The sample application uses an embedded MongoDB instance that listens on a randomly allocated port.
The port used by embedded MongoDB together with exact command to connect to it is logged during application startup.
====
$ mongo --port ...
> use test
> db.sessions.find().pretty()
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `60f17293-839b-477c-bb92-07a9c3658843` with the value of your SESSION cookie:
> db.sessions.remove({"_id":"60f17293-839b-477c-bb92-07a9c3658843"})
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.

View File

@@ -52,10 +52,6 @@ If you are looking to get started with Spring Session, the best place to start i
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
| link:guides/boot.html[HttpSession with Redis Guide]
| {gh-samples-url}boot/mongo[HttpSession with Mongo]
| Demonstrates how to use Spring Session to replace the `HttpSession` with Mongo.
| link:guides/boot-mongo.html[HttpSession with Mongo Guide]
| {gh-samples-url}boot/jdbc[HttpSession with JDBC]
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
| link:guides/boot-jdbc.html[HttpSession with JDBC Guide]
@@ -220,57 +216,6 @@ You can read the basic steps for integration below, but you are encouraged to fo
include::guides/boot-jdbc.adoc[tags=config,leveloffset=+3]
[[httpsession-mongo]]
=== HttpSession with Mongo
Using Spring Session with `HttpSession` is enabled by adding a Servlet Filter before anything that uses the `HttpSession`.
This section describes how to use Mongo to back `HttpSession` using Java based configuration.
NOTE: The <<samples, HttpSession Mongo Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession Guide when integrating with your own application.
include::guides/boot-mongo.adoc[tags=config,leveloffset=+3]
==== Session serialization mechanisms
To be able to persist session objects in MongoDB we need to provide the serialization/deserialization mechanism.
Depending on your classpath Spring Session will choose one of two build-in converters:
* `JacksonMongoSessionConverter` when `ObjectMapper` class is available, or
* `JdkMongoSessionConverter` otherwise.
===== JacksonMongoSessionConverter
This mechanism uses Jackson to serialize session objects to/from JSON.
`JacksonMongoSessionConverter` will be the default when Jackson is detected on the classpath and the user has not explicitly registered a `AbstractMongoSessionConverter` Bean.
If you would like to provide custom Jackson modules you can do it by explicitly registering `JacksonMongoSessionConverter`:
[source,java,indent=0]
----
include::{docs-test-dir}docs/http/MongoJacksonSessionConfiguration.java[tags=config]
----
==== JdkMongoSessionConverter
`JdkMongoSessionConverter` uses standard Java serialization to persist session attributes map to MongoDB in a binary form.
However, standard session elements like id, access time, etc are still written as a plain Mongo objects and can be read and queried without additional effort.
`JdkMongoSessionConverter` is used if Jackson is not on the classpath and no explicit `AbstractMongoSessionConverter` Bean has been defined.
You can explicitly register `JdkMongoSessionConverter` by defining it as a Bean.
[source,java,indent=0]
----
include::{docs-test-dir}docs/http/MongoJdkSessionConfiguration.java[tags=config]
----
There is also a constructor taking `Serializer` and `Deserializer` objects, allowing you to pass custom implementations, which is especially important when you want to use non-default classloader.
==== Using custom converters
You can create your own session converter by extending `AbstractMongoSessionConverter` class.
The implementation will be used for serializing, deserializing your objects and for providing queries to access the session.
[[httpsession-hazelcast]]
=== HttpSession with Hazelcast
@@ -480,7 +425,7 @@ include::{docs-test-resources-dir}docs/security/security-config.xml[tags=config]
----
This assumes that your Spring Session `SessionRegistry` bean is called `sessionRegistry`, which is the name used by all
`SpringHttpSessionConfiguration` subclasses except for the one for MongoDB: there it's called `mongoSessionRepository`.
`SpringHttpSessionConfiguration` subclasses.
[[spring-security-concurrent-sessions-limitations]]
=== Limitations

View File

@@ -1,48 +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
*
* http://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 docs.http;
import java.util.Collections;
import com.fasterxml.jackson.databind.Module;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
/**
*
* @author Jakub Kubrynski
* @author Rob Winch
*/
// tag::config[]
@Configuration
@EnableMongoHttpSession
public class MongoJacksonSessionConfiguration {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter(getJacksonModules());
}
public Iterable<Module> getJacksonModules() {
return Collections.<Module>singletonList(new MyJacksonModule());
}
}
// end::config[]

View File

@@ -1,40 +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
*
* http://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 docs.http;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
/**
*
* @author Jakub Kubrynski
* @author Rob Winch
*/
// tag::config[]
@Configuration
@EnableMongoHttpSession
public class MongoJdkSessionConfiguration {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JdkMongoSessionConverter();
}
}
// end::config[]

View File

@@ -1,22 +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
*
* http://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 docs.http;
import com.fasterxml.jackson.databind.module.SimpleModule;
class MyJacksonModule extends SimpleModule {
}

View File

@@ -16,9 +16,6 @@ configurations.spring3TestRuntime {
if (details.requested.name == 'spring-data-keyvalue') {
details.useVersion '1.1.1.RELEASE'
}
if (details.requested.name == 'spring-data-mongodb') {
details.useVersion '1.9.1.RELEASE'
}
if (details.requested.name == 'spring-data-redis') {
details.useVersion '1.7.1.RELEASE'
}

View File

@@ -1 +0,0 @@
Demonstrates using Spring Session with Spring Boot and Spring Security. You can log in with the username "user" and the password "password".

View File

@@ -1,19 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session')
compile "org.springframework.data:spring-data-mongodb"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"
compile "org.webjars:webjars-locator"
compile "de.flapdoodle.embed:de.flapdoodle.embed.mongo"
testCompile "org.springframework.boot:spring-boot-starter-test"
integrationTestCompile seleniumDependencies
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import sample.pages.HomePage;
import sample.pages.LoginPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class BootTests {
@Autowired
private MockMvc mockMvc;
private WebDriver driver;
@Before
public void setUp() {
this.driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(this.mockMvc)
.build();
}
@After
public void tearDown() {
this.driver.quit();
}
@Test
public void unauthenticatedUserSentToLogInPage() {
HomePage homePage = HomePage.go(this.driver);
LoginPage loginPage = homePage.unauthenticated();
loginPage.assertAt();
}
@Test
public void logInViewsHomePage() {
LoginPage loginPage = LoginPage.go(this.driver);
loginPage.assertAt();
HomePage homePage = loginPage.login("user", "password");
homePage.assertAt();
WebElement username = homePage.getDriver().findElement(By.id("un"));
assertThat(username.getText()).isEqualTo("user");
Set<Cookie> cookies = homePage.getDriver().manage().getCookies();
assertThat(cookies).extracting("name").contains("SESSION");
assertThat(cookies).extracting("name").doesNotContain("JSESSIONID");
}
@Test
public void logoutSuccess() {
LoginPage loginPage = LoginPage.go(this.driver);
HomePage homePage = loginPage.login("user", "password");
LoginPage successLogoutPage = homePage.logout();
successLogoutPage.assertAt();
}
@Test
public void loggedOutUserSentToLoginPage() {
LoginPage loginPage = LoginPage.go(this.driver);
HomePage homePage = loginPage.login("user", "password");
homePage.logout();
HomePage backHomePage = HomePage.go(this.driver);
LoginPage backLoginPage = backHomePage.unauthenticated();
backLoginPage.assertAt();
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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.pages;
import org.openqa.selenium.WebDriver;
/**
* @author Pool Dolorier
*/
public abstract class BasePage {
private WebDriver driver;
public BasePage(WebDriver driver) {
this.driver = driver;
}
public WebDriver getDriver() {
return this.driver;
}
public static void get(WebDriver driver, String get) {
String baseUrl = "http://localhost";
driver.get(baseUrl + get);
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class HomePage extends BasePage {
@FindBy(css = "input[type='submit']")
private WebElement submit;
public HomePage(WebDriver driver) {
super(driver);
}
public static HomePage go(WebDriver driver) {
get(driver, "/");
return PageFactory.initElements(driver, HomePage.class);
}
public LoginPage unauthenticated() {
return LoginPage.go(getDriver());
}
public LoginPage logout() {
this.submit.click();
return LoginPage.go(getDriver());
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Secured Content");
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class LoginPage extends BasePage {
@FindBy(name = "username")
private WebElement username;
@FindBy(name = "password")
private WebElement password;
@FindBy(css = "input[name='submit']")
private WebElement submit;
public LoginPage(WebDriver driver) {
super(driver);
}
public static LoginPage go(WebDriver driver) {
get(driver, "/login");
return PageFactory.initElements(driver, LoginPage.class);
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
}
public HomePage login(String user, String password) {
this.username.sendKeys(user);
this.password.sendKeys(password);
this.submit.click();
return HomePage.go(getDriver());
}
}

View File

@@ -1,30 +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
*
* http://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.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Rob Winch
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -1,44 +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
*
* http://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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
class EmbeddedMongoPortLogger implements ApplicationRunner, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(EmbeddedMongoPortLogger.class);
private Environment environment;
public void run(ApplicationArguments args) throws Exception {
String port = this.environment.getProperty("local.mongo.port");
logger.info("Embedded Mongo started on port " + port +
", use 'mongo --port " + port + "' command to connect");
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

View File

@@ -1,31 +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
*
* http://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.config;
import org.springframework.context.annotation.Bean;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
// tag::class[]
@EnableMongoHttpSession // <1>
public class HttpSessionConfig {
@Bean
public JdkMongoSessionConverter jdkMongoSessionConverter() {
return new JdkMongoSessionConverter(); // <2>
}
}
// end::class[]

View File

@@ -1,33 +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
*
* http://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.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author Rob Winch
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}

View File

@@ -1,33 +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
*
* http://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.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Controller for sending the user to the login view.
*
* @author Rob Winch
*
*/
@Controller
public class IndexController {
@RequestMapping("/")
public String index() {
return "index";
}
}

View File

@@ -1,3 +0,0 @@
spring.thymeleaf.cache=false
spring.template.cache=false
spring.data.mongodb.port=0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,11 +0,0 @@
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
<head>
<title>Secured Content</title>
</head>
<body>
<div layout:fragment="content">
<h1>Secured Page</h1>
<p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p>
</div>
</body>
</html>

View File

@@ -1,122 +0,0 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
<link rel="icon" type="image/x-icon" th:href="@{/resources/img/favicon.ico}" href="../static/img/favicon.ico"/>
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"></link>
<style type="text/css">
/* Sticky footer styles
-------------------------------------------------- */
html,
body {
height: 100%;
/* The html and body elements cannot have any padding or margin. */
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
/* Negative indent footer by it's height */
margin: 0 auto -60px;
}
/* Set the fixed height of the footer here */
#push,
#footer {
height: 60px;
}
#footer {
background-color: #f5f5f5;
}
/* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) {
#footer {
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
}
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
.container {
width: auto;
max-width: 680px;
}
.container .credit {
margin: 20px 0;
text-align: center;
}
a {
color: green;
}
.navbar-form {
margin-left: 1em;
}
</style>
<link th:href="@{/webjars/bootstrap/css/bootstrap-responsive.min.css}" href="/webjars/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet"></link>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script th:src="@{/webjars/html5shiv/html5shiv.min.js}" src="/webjars/html5shiv/html5shiv.min.js"></script>
<![endif]-->
</head>
<body>
<div id="wrap">
<div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/logo.png}" alt="Spring Security Sample"/></a>
<div class="nav-collapse collapse"
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
<div th:if="${currentUser != null}">
<form class="navbar-form pull-right" th:action="@{/logout}" method="post">
<input type="submit" value="Log out" />
</form>
<p id="un" class="navbar-text pull-right" th:text="${currentUser.username}">
sample_user
</p>
</div>
<ul class="nav">
</ul>
</div>
</div>
</div>
</div>
<!-- Begin page content -->
<div class="container">
<div class="alert alert-success"
th:if="${globalMessage}"
th:text="${globalMessage}">
Some Success message
</div>
<div layout:fragment="content">
Fake content
</div>
</div>
<div id="push"><!-- --></div>
</div>
<div id="footer">
<div class="container">
<p class="muted credit">Visit the <a href="http://spring.io/spring-security">Spring Security</a> site for more <a href="https://github.com/spring-projects/spring-security/blob/master/samples/">samples</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -1,8 +0,0 @@
apply plugin: 'io.spring.convention.spring-pom'
description = "Aggregator for Spring Session and Spring Data Mongo"
dependencies {
compile project(':spring-session')
compile "org.springframework.data:spring-data-mongodb"
}

View File

@@ -10,7 +10,6 @@ dependencies {
optional "org.springframework:spring-messaging"
optional "org.springframework:spring-web"
optional "org.springframework:spring-websocket"
optional "org.springframework.data:spring-data-mongodb"
optional "org.springframework.data:spring-data-redis"
optional "org.springframework.security:spring-security-core"
optional "org.springframework.security:spring-security-web"
@@ -24,8 +23,6 @@ dependencies {
integrationTestCompile "com.h2database:h2"
integrationTestCompile "com.hazelcast:hazelcast-client"
integrationTestCompile "org.hsqldb:hsqldb"
integrationTestCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo"
testCompile "junit:junit"
testCompile "org.mockito:mockito-core"

View File

@@ -1,397 +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
*
* http://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 org.springframework.session.data.mongo;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.mongodb.MongoClient;
import de.flapdoodle.embed.mongo.MongodExecutable;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.AbstractITests;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Abstract base class for {@link MongoOperationsSessionRepository} tests.
*
* @author Jakub Kubrynski
* @author Vedran Pavic
*/
abstract public class AbstractMongoRepositoryITests extends AbstractITests {
protected static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
protected static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
protected MongoOperationsSessionRepository repository;
@Test
public void saves() throws InterruptedException {
String username = "saves-" + System.currentTimeMillis();
MongoExpiringSession toSave = this.repository.createSession();
String expectedAttributeName = "a";
String expectedAttributeValue = "b";
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username,
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
toSaveContext.setAuthentication(toSaveToken);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext);
toSave.setAttribute(INDEX_NAME, username);
this.repository.save(toSave);
Session session = this.repository.getSession(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
assertThat(session.<String>getAttribute(expectedAttributeName))
.isEqualTo(toSave.getAttribute(expectedAttributeName));
this.repository.delete(toSave.getId());
String id = toSave.getId();
assertThat(this.repository.getSession(id)).isNull();
}
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute("a", "b");
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("1", "2");
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
Session session = this.repository.getSession(toSave.getId());
assertThat(session.getAttributeNames().size()).isEqualTo(2);
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
this.repository.delete(toSave.getId());
}
@Test
public void findByPrincipalName() throws Exception {
String principalName = "findByPrincipalName" + UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.delete(toSave.getId());
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChange() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChange"
+ UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception {
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload"
+ UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalName() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, null);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalName() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
toSave.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedPrincipalNameReload() throws Exception {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
MongoExpiringSession getSession = this.repository.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedPrincipalNameReload() throws Exception {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
MongoExpiringSession getSession = this.repository.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(getSession);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalNameChanged);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findBySecurityPrincipalName() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
this.repository.delete(toSave.getId());
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByDeletedSecurityPrincipalName() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, null);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByChangedSecurityPrincipalName() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void findByChangedSecurityPrincipalNameReload() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
MongoExpiringSession getSession = this.repository.getSession(toSave.getId());
getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(getSession);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Test
public void loadExpiredSession() throws Exception {
// given
MongoExpiringSession expiredSession = this.repository.createSession();
long thirtyOneMinutesAgo = System.currentTimeMillis()
- TimeUnit.MINUTES.toMillis(31);
expiredSession.setLastAccessedTime(thirtyOneMinutesAgo);
this.repository.save(expiredSession);
// then
MongoExpiringSession expiredSessionFromDb = this.repository
.getSession(expiredSession.getId());
assertThat(expiredSessionFromDb).isNull();
}
protected String getSecurityName() {
return this.context.getAuthentication().getName();
}
protected String getChangedSecurityName() {
return this.changedContext.getAuthentication().getName();
}
protected static class BaseConfig {
private int embeddedMongoPort = SocketUtils.findAvailableTcpPort();
@Bean(initMethod = "start", destroyMethod = "stop")
public MongodExecutable embeddedMongoServer() throws IOException {
return MongoITestUtils.embeddedMongoServer(this.embeddedMongoPort);
}
@Bean
@DependsOn("embeddedMongoServer")
public MongoOperations mongoOperations() throws UnknownHostException {
MongoClient mongo = new MongoClient("localhost", this.embeddedMongoPort);
return new MongoTemplate(mongo, "test");
}
}
}

View File

@@ -1,53 +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
*
* http://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 org.springframework.session.data.mongo;
import java.io.IOException;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
/**
* Utility class for Mongo integration tests.
*
* @author Vedran Pavic
*/
final class MongoITestUtils {
private MongoITestUtils() {
}
/**
* Creates {@link MongodExecutable} for use in integration tests.
* @param port the port for embedded Mongo to bind to
* @return the {@link MongodExecutable} instance
* @throws IOException in case of I/O errors
*/
static MongodExecutable embeddedMongoServer(int port) throws IOException {
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(port, Network.localhostIsIPv6()))
.build();
MongodStarter mongodStarter = MongodStarter.getDefaultInstance();
return mongodStarter.prepare(mongodConfig);
}
}

View File

@@ -1,68 +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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.Module;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.geo.GeoModule;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
import org.springframework.test.context.ContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link MongoOperationsSessionRepository} that use
* {@link JacksonMongoSessionConverter} based session serialization.
*
* @author Jakub Kubrynski
* @author Vedran Pavic
*/
@ContextConfiguration
public class MongoRepositoryJacksonITests extends AbstractMongoRepositoryITests {
@Test
public void findByCustomIndex() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
String cartId = "cart-" + UUID.randomUUID();
toSave.setAttribute("cartId", cartId);
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByCartId = this.repository
.findByIndexNameAndIndexValue("cartId", cartId);
assertThat(findByCartId).hasSize(1);
assertThat(findByCartId.keySet()).containsOnly(toSave.getId());
}
@Configuration
@EnableMongoHttpSession
static class Config extends BaseConfig {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter(Collections.<Module>singletonList(new GeoModule()));
}
}
}

View File

@@ -1,86 +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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
import org.springframework.test.context.ContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link MongoOperationsSessionRepository} that use
* {@link JacksonMongoSessionConverter} based session serialization.
*
* @author Jakub Kubrynski
* @author Vedran Pavic
*/
@ContextConfiguration
public class MongoRepositoryJdkSerializationITests extends AbstractMongoRepositoryITests {
@Test
public void findByDeletedSecurityPrincipalNameReload() throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
MongoExpiringSession getSession = this.repository.getSession(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload()
throws Exception {
MongoExpiringSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
toSave = this.repository.getSession(toSave.getId());
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, MongoExpiringSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
}
@Configuration
@EnableMongoHttpSession
static class Config extends BaseConfig {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JdkMongoSessionConverter();
}
}
}

View File

@@ -1,123 +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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.mongodb.DBObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.Document;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
/**
* Base class for serializing and deserializing session objects. To create custom
* serializer you have to implement this interface and simply register your class as a
* bean.
*
* @author Jakub Kubrynski
* @since 1.2
*/
public abstract class AbstractMongoSessionConverter implements GenericConverter {
private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class);
protected static final String EXPIRE_AT_FIELD_NAME = "expireAt";
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
/**
* Returns query to be executed to return sessions based on a particular index.
* @param indexName name of the index
* @param indexValue value to query against
* @return built query or null if indexName is not supported
*/
protected abstract Query getQueryForIndex(String indexName, Object indexValue);
/**
* Method ensures that there is a TTL index on {@literal expireAt} field. It's has
* {@literal expireAfterSeconds} set to zero seconds, so the expiration time is
* controlled by the application.
*
* It can be extended in custom converters when there is a need for creating
* additional custom indexes.
* @param sessionCollectionIndexes {@link IndexOperations} to use
*/
protected void ensureIndexes(IndexOperations sessionCollectionIndexes) {
List<IndexInfo> indexInfo = sessionCollectionIndexes.getIndexInfo();
for (IndexInfo info : indexInfo) {
if (EXPIRE_AT_FIELD_NAME.equals(info.getName())) {
LOG.debug(
"TTL index on field " + EXPIRE_AT_FIELD_NAME + " already exists");
return;
}
}
LOG.info("Creating TTL index on field " + EXPIRE_AT_FIELD_NAME);
sessionCollectionIndexes
.ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC)
.named(EXPIRE_AT_FIELD_NAME).expire(0));
}
protected String extractPrincipal(Session expiringSession) {
String resolvedPrincipal = AuthenticationParser
.extractName(expiringSession.getAttribute(SPRING_SECURITY_CONTEXT));
if (resolvedPrincipal != null) {
return resolvedPrincipal;
}
else {
return expiringSession.getAttribute(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
}
}
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(DBObject.class, MongoExpiringSession.class));
}
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if (DBObject.class.isAssignableFrom(sourceType.getType())) {
return convert(new Document(((DBObject) source).toMap()));
}
else if (Document.class.isAssignableFrom(sourceType.getType())) {
return convert((Document) source);
}
else {
return convert((MongoExpiringSession) source);
}
}
protected abstract DBObject convert(MongoExpiringSession session);
protected abstract MongoExpiringSession convert(Document sessionWrapper);
}

View File

@@ -1,48 +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
*
* http://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 org.springframework.session.data.mongo;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Utility class to extract principal name from {@code Authentication} object.
*
* @author Jakub Kubrynski
*/
final class AuthenticationParser {
private static final String NAME_EXPRESSION = "authentication?.name";
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
/**
* Extracts principal name from authentication.
*
* @param authentication Authentication object
* @return principal name
*/
static String extractName(Object authentication) {
if (authentication != null) {
Expression expression = PARSER.parseExpression(NAME_EXPRESSION);
return expression.getValue(authentication, String.class);
}
return null;
}
private AuthenticationParser() {
}
}

View File

@@ -1,124 +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
*
* http://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 org.springframework.session.data.mongo;
import java.io.IOException;
import java.util.Collections;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.FindByIndexNameSessionRepository;
/**
* {@code AbstractMongoSessionConverter} implementation using Jackson.
*
* @author Jakub Kubrynski
* @since 1.2
*/
public class JacksonMongoSessionConverter extends AbstractMongoSessionConverter {
private static final Log LOG = LogFactory.getLog(JacksonMongoSessionConverter.class);
private static final String ATTRS_FIELD_NAME = "attrs.";
private static final String PRINCIPAL_FIELD_NAME = "principal";
private final ObjectMapper objectMapper;
public JacksonMongoSessionConverter() {
this(Collections.<Module>emptyList());
}
public JacksonMongoSessionConverter(Iterable<Module> modules) {
this.objectMapper = buildObjectMapper();
this.objectMapper.registerModules(modules);
}
protected Query getQueryForIndex(String indexName, Object indexValue) {
if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
.equals(indexName)) {
return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue));
}
return Query.query(Criteria.where(ATTRS_FIELD_NAME +
MongoExpiringSession.coverDot(indexName)).is(indexValue));
}
private ObjectMapper buildObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// serialize fields instead of properties
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
// ignore unresolved fields (mostly 'principal')
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new MongoIdNamingStrategy());
return objectMapper;
}
@Override
protected DBObject convert(MongoExpiringSession source) {
try {
DBObject dbSession = (DBObject) JSON.parse(this.objectMapper.writeValueAsString(source));
dbSession.put(PRINCIPAL_FIELD_NAME, extractPrincipal(source));
return dbSession;
}
catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot convert MongoExpiringSession", e);
}
}
@Override
protected MongoExpiringSession convert(Document source) {
String json = JSON.serialize(source);
try {
return this.objectMapper.readValue(json, MongoExpiringSession.class);
}
catch (IOException e) {
LOG.error("Error during Mongo Session deserialization", e);
return null;
}
}
private static class MongoIdNamingStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {
@Override
public String translate(String propertyName) {
if (propertyName.equals("id")) {
return "_id";
}
else if (propertyName.equals("_id")) {
return "id";
}
return propertyName;
}
}
}

View File

@@ -1,123 +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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.Document;
import org.bson.types.Binary;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
/**
* {@code AbstractMongoSessionConverter} implementation using standard Java serialization.
*
* @author Jakub Kubrynski
* @author Rob Winch
* @since 1.2
*/
public class JdkMongoSessionConverter extends AbstractMongoSessionConverter {
private static final String ID = "_id";
private static final String CREATION_TIME = "created";
private static final String LAST_ACCESSED_TIME = "accessed";
private static final String MAX_INTERVAL = "interval";
static final String ATTRIBUTES = "attr";
private static final String PRINCIPAL_FIELD_NAME = "principal";
private final Converter<Object, byte[]> serializer;
private final Converter<byte[], Object> deserializer;
public JdkMongoSessionConverter() {
this(new SerializingConverter(), new DeserializingConverter());
}
public JdkMongoSessionConverter(Converter<Object, byte[]> serializer,
Converter<byte[], Object> deserializer) {
Assert.notNull(serializer, "serializer cannot be null");
Assert.notNull(deserializer, "deserializer cannot be null");
this.serializer = serializer;
this.deserializer = deserializer;
}
@Override
public Query getQueryForIndex(String indexName, Object indexValue) {
if (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
.equals(indexName)) {
return Query.query(Criteria.where(PRINCIPAL_FIELD_NAME).is(indexValue));
}
return null;
}
@Override
protected DBObject convert(MongoExpiringSession session) {
BasicDBObject basicDBObject = new BasicDBObject();
basicDBObject.put(ID, session.getId());
basicDBObject.put(CREATION_TIME, session.getCreationTime());
basicDBObject.put(LAST_ACCESSED_TIME, session.getLastAccessedTime());
basicDBObject.put(MAX_INTERVAL, session.getMaxInactiveIntervalInSeconds());
basicDBObject.put(PRINCIPAL_FIELD_NAME, extractPrincipal(session));
basicDBObject.put(EXPIRE_AT_FIELD_NAME, session.getExpireAt());
basicDBObject.put(ATTRIBUTES, serializeAttributes(session));
return basicDBObject;
}
@Override
protected MongoExpiringSession convert(Document sessionWrapper) {
MongoExpiringSession session = new MongoExpiringSession(
sessionWrapper.getString(ID), sessionWrapper.getInteger(MAX_INTERVAL));
session.setCreationTime(sessionWrapper.getLong(CREATION_TIME));
session.setLastAccessedTime(sessionWrapper.getLong(LAST_ACCESSED_TIME));
session.setExpireAt((Date) sessionWrapper.get(EXPIRE_AT_FIELD_NAME));
deserializeAttributes(sessionWrapper, session);
return session;
}
private byte[] serializeAttributes(Session session) {
Map<String, Object> attributes = new HashMap<String, Object>();
for (String attrName : session.getAttributeNames()) {
attributes.put(attrName, session.getAttribute(attrName));
}
return this.serializer.convert(attributes);
}
@SuppressWarnings("unchecked")
private void deserializeAttributes(Document sessionWrapper, Session session) {
Object sessionAttributes = sessionWrapper.get(ATTRIBUTES);
byte[] attributesBytes = (sessionAttributes instanceof Binary
? ((Binary) sessionAttributes).getData() : (byte[]) sessionAttributes);
Map<String, Object> attributes = (Map<String, Object>) this.deserializer.convert(attributesBytes);
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
session.setAttribute(entry.getKey(), entry.getValue());
}
}
}

View File

@@ -1,156 +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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.springframework.session.ExpiringSession;
/**
* Session object providing additional information about the datetime of expiration.
*
* @author Jakub Kubrynski
* @since 1.2
*/
public class MongoExpiringSession implements ExpiringSession {
/**
* Mongo doesn't support {@literal dot} in field names. We replace it with very rarely used character
*/
private static final char DOT_COVER_CHAR = '\uF607';
private final String id;
private long created = System.currentTimeMillis();
private long accessed;
private int interval;
private Date expireAt;
private Map<String, Object> attrs = new HashMap<String, Object>();
public MongoExpiringSession() {
this(MongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL);
}
public MongoExpiringSession(int maxInactiveIntervalInSeconds) {
this(UUID.randomUUID().toString(), maxInactiveIntervalInSeconds);
}
public MongoExpiringSession(String id, int maxInactiveIntervalInSeconds) {
this.id = id;
this.interval = maxInactiveIntervalInSeconds;
setLastAccessedTime(this.created);
}
public String getId() {
return this.id;
}
@SuppressWarnings("unchecked")
public <T> T getAttribute(String attributeName) {
return (T) this.attrs.get(coverDot(attributeName));
}
public Set<String> getAttributeNames() {
HashSet<String> result = new HashSet<String>();
for (String key : this.attrs.keySet()) {
result.add(uncoverDot(key));
}
return result;
}
public void setAttribute(String attributeName, Object attributeValue) {
if (attributeValue == null) {
removeAttribute(coverDot(attributeName));
}
else {
this.attrs.put(coverDot(attributeName), attributeValue);
}
}
public void removeAttribute(String attributeName) {
this.attrs.remove(coverDot(attributeName));
}
public long getCreationTime() {
return this.created;
}
public void setCreationTime(long created) {
this.created = created;
}
public void setLastAccessedTime(long lastAccessedTime) {
this.accessed = lastAccessedTime;
this.expireAt = new Date(
lastAccessedTime + TimeUnit.SECONDS.toMillis(this.interval));
}
public long getLastAccessedTime() {
return this.accessed;
}
public void setMaxInactiveIntervalInSeconds(int interval) {
this.interval = interval;
}
public int getMaxInactiveIntervalInSeconds() {
return this.interval;
}
public boolean isExpired() {
return this.interval >= 0 && new Date().after(this.expireAt);
}
public Date getExpireAt() {
return this.expireAt;
}
public void setExpireAt(Date expireAt) {
this.expireAt = expireAt;
}
static String coverDot(String attributeName) {
return attributeName.replace('.', DOT_COVER_CHAR);
}
static String uncoverDot(String attributeName) {
return attributeName.replace(DOT_COVER_CHAR, '.');
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MongoExpiringSession that = (MongoExpiringSession) o;
return this.id.equals(that.id);
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.mongodb.DBObject;
import org.bson.Document;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.FindByIndexNameSessionRepository;
/**
* Session repository implementation which stores sessions in Mongo. Uses
* {@link AbstractMongoSessionConverter} to transform session objects from/to native Mongo
* representation ({@code DBObject}).
*
* Repository is also responsible for removing expired sessions from database. Cleanup is
* done every minute.
*
* @author Jakub Kubrynski
* @since 1.2
*/
public class MongoOperationsSessionRepository
implements FindByIndexNameSessionRepository<MongoExpiringSession> {
/**
* The default time period in seconds in which a session will expire.
*/
public static final int DEFAULT_INACTIVE_INTERVAL = 1800;
/**
* the default collection name for storing session.
*/
public static final String DEFAULT_COLLECTION_NAME = "sessions";
private final MongoOperations mongoOperations;
private AbstractMongoSessionConverter mongoSessionConverter =
SessionConverterProvider.getDefaultMongoConverter();
private Integer maxInactiveIntervalInSeconds = DEFAULT_INACTIVE_INTERVAL;
private String collectionName = DEFAULT_COLLECTION_NAME;
public MongoOperationsSessionRepository(MongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
public MongoExpiringSession createSession() {
MongoExpiringSession session = new MongoExpiringSession();
if (this.maxInactiveIntervalInSeconds != null) {
session.setMaxInactiveIntervalInSeconds(this.maxInactiveIntervalInSeconds);
}
return session;
}
public void save(MongoExpiringSession session) {
DBObject sessionDbObject = convertToDBObject(session);
this.mongoOperations.save(sessionDbObject, this.collectionName);
}
public MongoExpiringSession getSession(String id) {
Document sessionWrapper = findSession(id);
if (sessionWrapper == null) {
return null;
}
MongoExpiringSession session = convertToSession(sessionWrapper);
if (session.isExpired()) {
delete(id);
return null;
}
return session;
}
/**
* Currently this repository allows only querying against
* {@code PRINCIPAL_NAME_INDEX_NAME}.
*
* @param indexName the name if the index (i.e.
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME})
* @param indexValue the value of the index to search for.
* @return sessions map
*/
public Map<String, MongoExpiringSession> findByIndexNameAndIndexValue(
String indexName, String indexValue) {
HashMap<String, MongoExpiringSession> result = new HashMap<String, MongoExpiringSession>();
Query query = this.mongoSessionConverter.getQueryForIndex(indexName, indexValue);
if (query == null) {
return Collections.emptyMap();
}
List<Document> mapSessions = this.mongoOperations.find(query, Document.class, this.collectionName);
for (Document dbSession : mapSessions) {
MongoExpiringSession mapSession = convertToSession(dbSession);
result.put(mapSession.getId(), mapSession);
}
return result;
}
public void delete(String id) {
this.mongoOperations.remove(findSession(id), this.collectionName);
}
@PostConstruct
public void ensureIndexesAreCreated() {
IndexOperations indexOperations = this.mongoOperations
.indexOps(this.collectionName);
this.mongoSessionConverter.ensureIndexes(indexOperations);
}
Document findSession(String id) {
return this.mongoOperations.findById(id, Document.class, this.collectionName);
}
MongoExpiringSession convertToSession(Document session) {
return (MongoExpiringSession) this.mongoSessionConverter.convert(session,
TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoExpiringSession.class));
}
DBObject convertToDBObject(MongoExpiringSession session) {
return (DBObject) this.mongoSessionConverter.convert(session,
TypeDescriptor.valueOf(MongoExpiringSession.class),
TypeDescriptor.valueOf(DBObject.class));
}
public void setMongoSessionConverter(
AbstractMongoSessionConverter mongoSessionConverter) {
this.mongoSessionConverter = mongoSessionConverter;
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}

View File

@@ -1,41 +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
*
* http://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 org.springframework.session.data.mongo;
import org.springframework.util.ClassUtils;
/**
* Provider choosing proper AbstractMongoSessionConverter.
*
* @author Jakub Kubrynski
*/
final class SessionConverterProvider {
private static final String JACKSON_CLASS_NAME = "com.fasterxml.jackson.databind.ObjectMapper";
private SessionConverterProvider() {
}
static AbstractMongoSessionConverter getDefaultMongoConverter() {
if (ClassUtils.isPresent(JACKSON_CLASS_NAME, null)) {
return new JacksonMongoSessionConverter();
}
else {
return new JdkMongoSessionConverter();
}
}
}

View File

@@ -1,68 +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
*
* http://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 org.springframework.session.data.mongo.config.annotation.web.http;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.data.mongo.MongoOperationsSessionRepository;
/**
* Add this annotation to a {@code @Configuration} class to expose the
* SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by
* Mongo. Use {@code collectionName} to change default name of the collection used to
* store sessions. <pre>
* <code>
* {@literal @EnableMongoHttpSession}
* public class MongoHttpSessionConfig {
*
* {@literal @Bean}
* public MongoOperations mongoOperations() throws UnknownHostException {
* return new MongoTemplate(new MongoClient(), "databaseName");
* }
*
* }
* </code> </pre>
*
* @author Jakub Kubrynski
* @since 1.2
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MongoHttpSessionConfiguration.class)
@Configuration
public @interface EnableMongoHttpSession {
/**
* The maximum time a session will be kept if it is inactive.
*
* @return default max inactive interval in seconds
*/
int maxInactiveIntervalInSeconds() default MongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL;
/**
* The collection name to use.
*
* @return name of the collection to store session
*/
String collectionName() default MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME;
}

View File

@@ -1,95 +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
*
* http://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 org.springframework.session.data.mongo.config.annotation.web.http;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.MongoOperationsSessionRepository;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* Configuration class registering {@code MongoSessionRepository} bean. To import this
* configuration use {@link EnableMongoHttpSession} annotation.
*
* @author Jakub Kubrynski
* @author Eddú Meléndez
* @since 1.2
*/
@Configuration
public class MongoHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements EmbeddedValueResolverAware, ImportAware {
private AbstractMongoSessionConverter mongoSessionConverter;
private Integer maxInactiveIntervalInSeconds;
private String collectionName;
private StringValueResolver embeddedValueResolver;
@Bean
public MongoOperationsSessionRepository mongoSessionRepository(
MongoOperations mongoOperations) {
MongoOperationsSessionRepository repository = new MongoOperationsSessionRepository(
mongoOperations);
repository.setMaxInactiveIntervalInSeconds(this.maxInactiveIntervalInSeconds);
if (this.mongoSessionConverter != null) {
repository.setMongoSessionConverter(this.mongoSessionConverter);
}
if (StringUtils.hasText(this.collectionName)) {
repository.setCollectionName(this.collectionName);
}
return repository;
}
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}
public void setImportMetadata(AnnotationMetadata importMetadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importMetadata
.getAnnotationAttributes(EnableMongoHttpSession.class.getName()));
this.maxInactiveIntervalInSeconds = attributes
.getNumber("maxInactiveIntervalInSeconds");
String collectionNameValue = attributes.getString("collectionName");
if (StringUtils.hasText(collectionNameValue)) {
this.collectionName = this.embeddedValueResolver.resolveStringValue(collectionNameValue);
}
}
@Autowired(required = false)
public void setMongoSessionConverter(
AbstractMongoSessionConverter mongoSessionConverter) {
this.mongoSessionConverter = mongoSessionConverter;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
}

View File

@@ -1,45 +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
*
* http://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 org.springframework.session.data.mongo;
import org.junit.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jakub Kubrynski
*/
public class AuthenticationParserTests {
@Test
public void shouldExtractName() {
// given
String principalName = "john_the_springer";
SecurityContextImpl context = new SecurityContextImpl();
context.setAuthentication(
new UsernamePasswordAuthenticationToken(principalName, null));
// when
String extractedName = AuthenticationParser.extractName(context);
// then
assertThat(extractedName).isEqualTo(principalName);
}
}

View File

@@ -1,53 +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
*
* http://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 org.springframework.session.data.mongo;
import com.mongodb.DBObject;
import org.junit.Test;
import org.springframework.data.mongodb.core.query.Query;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jakub Kubrynski
*/
public class JacksonMongoSessionConverterTest {
JacksonMongoSessionConverter sut = new JacksonMongoSessionConverter();
@Test
public void shouldSaveIdField() throws Exception {
//given
MongoExpiringSession session = new MongoExpiringSession();
//when
DBObject convert = this.sut.convert(session);
//then
assertThat(convert.get("_id")).isEqualTo(session.getId());
assertThat(convert.get("id")).isNull();
}
@Test
public void shouldQueryAgainstAttribute() throws Exception {
//when
Query cart = this.sut.getQueryForIndex("cart", "my-cart");
//then
assertThat(cart.getQueryObject().get("attrs.cart")).isEqualTo("my-cart");
}
}

View File

@@ -1,107 +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
*
* http://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 org.springframework.session.data.mongo;
import com.mongodb.DBObject;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jakub Kubrynski
* @author Rob Winch
*/
public class JdkMongoSessionConverterTests {
JdkMongoSessionConverter sut = new JdkMongoSessionConverter();
@Test(expected = IllegalArgumentException.class)
public void constructorNullSerializer() {
new JdkMongoSessionConverter(null, new DeserializingConverter());
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullDeserializer() {
new JdkMongoSessionConverter(new SerializingConverter(), null);
}
@Test
public void verifyRoundTripSerialization() throws Exception {
// given
MongoExpiringSession toSerialize = new MongoExpiringSession();
toSerialize.setAttribute("username", "john_the_springer");
// when
DBObject dbObject = convertToDBObject(toSerialize);
ExpiringSession deserialized = convertToSession(dbObject);
// then
assertThat(deserialized).isEqualToComparingFieldByField(toSerialize);
}
@Test
public void shouldExtractPrincipalNameFromAttributes() throws Exception {
// given
MongoExpiringSession toSerialize = new MongoExpiringSession();
String principalName = "john_the_springer";
toSerialize.setAttribute(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
principalName);
// when
DBObject dbObject = convertToDBObject(toSerialize);
// then
assertThat(dbObject.get("principal")).isEqualTo(principalName);
}
@Test
public void shouldExtractPrincipalNameFromAuthentication() throws Exception {
// given
MongoExpiringSession toSerialize = new MongoExpiringSession();
String principalName = "john_the_springer";
SecurityContextImpl context = new SecurityContextImpl();
context.setAuthentication(
new UsernamePasswordAuthenticationToken(principalName, null));
toSerialize.setAttribute("SPRING_SECURITY_CONTEXT", context);
// when
DBObject dbObject = convertToDBObject(toSerialize);
// then
assertThat(dbObject.get("principal")).isEqualTo(principalName);
}
MongoExpiringSession convertToSession(DBObject session) {
return (MongoExpiringSession) this.sut.convert(session,
TypeDescriptor.valueOf(DBObject.class),
TypeDescriptor.valueOf(MongoExpiringSession.class));
}
DBObject convertToDBObject(MongoExpiringSession session) {
return (DBObject) this.sut.convert(session,
TypeDescriptor.valueOf(MongoExpiringSession.class),
TypeDescriptor.valueOf(DBObject.class));
}
}

View File

@@ -1,36 +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
*
* http://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 org.springframework.session.data.mongo;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
*/
public class MongoExpiringSessionTests {
@Test
public void isExpiredWhenIntervalNegativeThenFalse() {
MongoExpiringSession session = new MongoExpiringSession();
session.setMaxInactiveIntervalInSeconds(-1);
session.setLastAccessedTime(0L);
assertThat(session.isExpired()).isFalse();
}
}

View File

@@ -1,210 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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 org.springframework.session.data.mongo;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link MongoOperationsSessionRepository}.
*
* @author Jakub Kubrynski
* @author Vedran Pavic
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoOperationsSessionRepositoryTests {
@Mock
private AbstractMongoSessionConverter converter;
@Mock
private MongoOperations mongoOperations;
private MongoOperationsSessionRepository repository;
@Before
public void setUp() throws Exception {
this.repository = new MongoOperationsSessionRepository(this.mongoOperations);
this.repository.setMongoSessionConverter(this.converter);
}
@Test
public void shouldCreateSession() throws Exception {
// when
ExpiringSession session = this.repository.createSession();
// then
assertThat(session.getId()).isNotEmpty();
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo(MongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL);
}
@Test
public void shouldCreateSessionWhenMaxInactiveIntervalNotDefined() throws Exception {
// when
this.repository.setMaxInactiveIntervalInSeconds(null);
ExpiringSession session = this.repository.createSession();
// then
assertThat(session.getId()).isNotEmpty();
assertThat(session.getMaxInactiveIntervalInSeconds())
.isEqualTo(MongoOperationsSessionRepository.DEFAULT_INACTIVE_INTERVAL);
}
@Test
public void shouldSaveSession() throws Exception {
// given
MongoExpiringSession session = new MongoExpiringSession();
BasicDBObject dbSession = new BasicDBObject();
given(this.converter.convert(session,
TypeDescriptor.valueOf(MongoExpiringSession.class),
TypeDescriptor.valueOf(DBObject.class))).willReturn(dbSession);
// when
this.repository.save(session);
// then
verify(this.mongoOperations).save(dbSession, MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME);
}
@Test
public void shouldGetSession() throws Exception {
// given
String sessionId = UUID.randomUUID().toString();
Document sessionDocument = new Document();
given(this.mongoOperations.findById(sessionId, Document.class,
MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(sessionDocument);
MongoExpiringSession session = new MongoExpiringSession();
given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoExpiringSession.class))).willReturn(session);
// when
ExpiringSession retrievedSession = this.repository.getSession(sessionId);
// then
assertThat(retrievedSession).isEqualTo(session);
}
@Test
public void shouldHandleExpiredSession() throws Exception {
// given
String sessionId = UUID.randomUUID().toString();
Document sessionDocument = new Document();
given(this.mongoOperations.findById(sessionId, Document.class,
MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(sessionDocument);
MongoExpiringSession session = mock(MongoExpiringSession.class);
given(session.isExpired()).willReturn(true);
given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoExpiringSession.class))).willReturn(session);
// when
this.repository.getSession(sessionId);
// then
verify(this.mongoOperations).remove(any(Document.class),
eq(MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME));
}
@Test
public void shouldDeleteSession() throws Exception {
// given
String sessionId = UUID.randomUUID().toString();
Document sessionDocument = new Document();
given(this.mongoOperations.findById(eq(sessionId), eq(Document.class),
eq(MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME))).willReturn(sessionDocument);
// when
this.repository.delete(sessionId);
// then
verify(this.mongoOperations).remove(any(Document.class),
eq(MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME));
}
@Test
public void shouldGetSessionsMapByPrincipal() throws Exception {
// given
String principalNameIndexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
Document document = new Document();
given(this.converter.getQueryForIndex(anyString(), any(Object.class))).willReturn(mock(Query.class));
given(this.mongoOperations.find(any(Query.class), eq(Document.class),
eq(MongoOperationsSessionRepository.DEFAULT_COLLECTION_NAME)))
.willReturn(Collections.singletonList(document));
String sessionId = UUID.randomUUID().toString();
MongoExpiringSession session = new MongoExpiringSession(sessionId, 1800);
given(this.converter.convert(document, TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoExpiringSession.class))).willReturn(session);
// when
Map<String, MongoExpiringSession> sessionsMap =
this.repository.findByIndexNameAndIndexValue(principalNameIndexName, "john");
// then
assertThat(sessionsMap).containsOnlyKeys(sessionId);
assertThat(sessionsMap).containsValues(session);
}
@Test
public void shouldReturnEmptyMapForNotSupportedIndex() throws Exception {
// given
String index = "some_not_supported_index_name";
// when
Map<String, MongoExpiringSession> sessionsMap = this.repository
.findByIndexNameAndIndexValue(index, "some_value");
// then
assertThat(sessionsMap).isEmpty();
}
}

View File

@@ -1,237 +0,0 @@
/*
* Copyright 2014-2017 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
*
* http://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 org.springframework.session.data.mongo.config.annotation.web.http;
import java.net.UnknownHostException;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.MongoOperationsSessionRepository;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MongoHttpSessionConfiguration}.
*
* @author Eddú Meléndez
* @author Vedran Pavic
*/
public class MongoHttpSessionConfigurationTests {
private static final String COLLECTION_NAME = "testSessions";
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
@Rule
public final ExpectedException thrown = ExpectedException.none();
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@After
public void after() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void noMongoOperationsConfiguration() {
this.thrown.expect(UnsatisfiedDependencyException.class);
this.thrown.expectMessage("mongoSessionRepository");
registerAndRefresh(EmptyConfiguration.class);
}
@Test
public void defaultConfiguration() {
registerAndRefresh(DefaultConfiguration.class);
assertThat(this.context.getBean(MongoOperationsSessionRepository.class))
.isNotNull();
}
@Test
public void customCollectionName() {
registerAndRefresh(CustomCollectionNameConfiguration.class);
MongoOperationsSessionRepository repository = this.context
.getBean(MongoOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "collectionName"))
.isEqualTo(COLLECTION_NAME);
}
@Test
public void setCustomCollectionName() {
registerAndRefresh(CustomCollectionNameSetConfiguration.class);
MongoHttpSessionConfiguration session = this.context
.getBean(MongoHttpSessionConfiguration.class);
assertThat(session).isNotNull();
assertThat(ReflectionTestUtils.getField(session, "collectionName"))
.isEqualTo(COLLECTION_NAME);
}
@Test
public void customMaxInactiveIntervalInSeconds() {
registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
MongoOperationsSessionRepository repository = this.context
.getBean(MongoOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "maxInactiveIntervalInSeconds"))
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@Test
public void setCustomMaxInactiveIntervalInSeconds() {
registerAndRefresh(CustomMaxInactiveIntervalInSecondsSetConfiguration.class);
MongoOperationsSessionRepository repository = this.context
.getBean(MongoOperationsSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "maxInactiveIntervalInSeconds"))
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@Test
public void setCustomSessionConverterConfiguration() {
registerAndRefresh(CustomSessionConverterConfiguration.class);
MongoOperationsSessionRepository repository = this.context
.getBean(MongoOperationsSessionRepository.class);
AbstractMongoSessionConverter mongoSessionConverter = this.context
.getBean(AbstractMongoSessionConverter.class);
assertThat(repository).isNotNull();
assertThat(mongoSessionConverter).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "mongoSessionConverter"))
.isEqualTo(mongoSessionConverter);
}
@Test
public void resolveCollectionNameByPropertyPlaceholder() {
this.context.setEnvironment(new MockEnvironment().withProperty("session.mongo.collectionName", COLLECTION_NAME));
registerAndRefresh(CustomMongoJdbcSessionConfiguration.class);
MongoHttpSessionConfiguration configuration = this.context.getBean(MongoHttpSessionConfiguration.class);
assertThat(ReflectionTestUtils.getField(configuration, "collectionName")).isEqualTo(COLLECTION_NAME);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
}
@Configuration
@EnableMongoHttpSession
static class EmptyConfiguration {
}
static class BaseConfiguration {
@Bean
public MongoOperations mongoOperations() throws UnknownHostException {
MongoOperations mongoOperations = mock(MongoOperations.class);
IndexOperations indexOperations = mock(IndexOperations.class);
given(mongoOperations.indexOps(anyString())).willReturn(indexOperations);
return mongoOperations;
}
}
@Configuration
@EnableMongoHttpSession
static class DefaultConfiguration extends BaseConfiguration {
}
@Configuration
static class MongoConfiguration extends BaseConfiguration {
}
@Configuration
@EnableMongoHttpSession(collectionName = COLLECTION_NAME)
static class CustomCollectionNameConfiguration extends BaseConfiguration {
}
@Configuration
@Import(MongoConfiguration.class)
static class CustomCollectionNameSetConfiguration extends MongoHttpSessionConfiguration {
CustomCollectionNameSetConfiguration() {
setCollectionName(COLLECTION_NAME);
}
}
@Configuration
@EnableMongoHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
static class CustomMaxInactiveIntervalInSecondsConfiguration extends BaseConfiguration {
}
@Configuration
@Import(MongoConfiguration.class)
static class CustomMaxInactiveIntervalInSecondsSetConfiguration extends MongoHttpSessionConfiguration {
CustomMaxInactiveIntervalInSecondsSetConfiguration() {
setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
}
@Configuration
@Import(MongoConfiguration.class)
static class CustomSessionConverterConfiguration extends MongoHttpSessionConfiguration {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return mock(AbstractMongoSessionConverter.class);
}
}
@Configuration
@EnableMongoHttpSession(collectionName = "${session.mongo.collectionName}")
static class CustomMongoJdbcSessionConfiguration extends BaseConfiguration {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
}