Align HttpSessionStrategy with WebSessionIdResolver

This commit simplifies `HttpSessionStrategy` API by aligning it with Spring Framework's `WebSessionIdResolver`. As a part of this, support for managing multiple users' sessions has been removed.

Closes gh-275
Closes gh-362
This commit is contained in:
Vedran Pavic
2017-10-25 20:42:29 +02:00
committed by Rob Winch
parent 2ecb2e60c0
commit cd394bbe10
33 changed files with 176 additions and 2635 deletions

View File

@@ -1,162 +0,0 @@
= Spring Session - Multiple Sessions
Rob Winch
:toc:
This guide describes how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
== Integrating with Spring Session
The steps to integrate with Spring Session are exactly the same as those outline in the link:httpsession.html[HttpSession Guide], so we will skip to running the sample application.
[[users-sample]]
== users Sample Application
The users application demonstrates how to allow an application to manage multiple simultaneous browser sessions (i.e. Google Accounts).
=== Running the users Sample Application
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
[NOTE]
====
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
====
----
$ ./gradlew :samples:users:tomcatRun
----
You should now be able to access the application at http://localhost:8080/
=== Exploring the users Sample Application
Try using the application. Authenticate with the following information:
* **Username** _rob_
* **Password** _rob_
Now click the **Login** button. You should now be authenticated as the user **rob**.
We can click on links and our user information is preserved.
* Click on the **Link** link in the navigation bar at the top
* Observe we are still authenticated as **rob**
Let's add an another account.
* Return to the *Home* page
* Click on the arrow next to *rob* in the upper right hand corner
* Click **Add Account**
The log in form is displayed again. Authenticate with the following information:
* **Username** _luke_
* **Password** _luke_
Now click the **Login** button. You should now be authenticated as the user **luke**.
We can click on links and our user information is preserved.
* Click on the **Link** link in the navigation bar at the top
* Observe we are still authenticated as **luke**
Where did our original user go? Let's switch to our original account.
* Click on the arrow next to *luke* in the upper right hand corner.
* Click on **Switch Account** -> *rob*
We are now using the session associated with *rob*.
== How does it work?
// tag::how-does-it-work[]
Let's take a look at how Spring Session keeps track of multiple sessions.
=== Managing a Single Session
Spring Session keeps track of the `HttpSession` by adding a value to a cookie named SESSION.
For example, the SESSION cookie might have a value of:
7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
=== Adding a Session
We can add another session by requesting a URL that contains a special parameter in it.
By default the parameter name is *_s*. For example, the following URL would create a new session:
http://localhost:8080/?_s=1
NOTE: The parameter value does not indicate the actual session id.
This is important because we never want to allow the session id to be determined by a client to avoid https://www.owasp.org/index.php/Session_fixation[session fixation attacks].
Additionally, we do not want the session id to be leaked since it is sent as a query parameter.
Remember sensitive information should only be transmitted as a header or in the body of the request.
Rather than creating the URL ourselves, we can utilize the `HttpSessionManager` to do this for us.
We can obtain the `HttpSessionManager` from the `HttpServletRequest` using the following:
.src/main/java/sample/UserAccountsFilter.java
[source,java,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=HttpSessionManager]
----
We can now use it to create a URL to add another session.
.src/main/java/sample/UserAccountsFilter.java
[source,java,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=addAccountUrl]
----
<1> We have an existing variable named `unauthenticatedAlias`.
The value is an alias that points to an existing unauthenticated session.
If no such session exists, the value is null.
This ensures if we have an existing unauthenticated session that we use it instead of creating a new session.
<2> If all of our sessions are already associated to a user, we create a new session alias.
<3> If there is an existing session that is not associated to a user, we use its session alias.
<4> Finally, we create the add account URL.
The URL contains a session alias that either points to an existing unauthenticated session or is an alias that is unused thus signaling to create a new session associated to that alias.
Now our SESSION cookie looks something like this:
0 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e 1 1d526d4a-c462-45a4-93d9-84a39b6d44ad
Such that:
* There is a session with the id *7e8383a4-082c-4ffe-a4bc-c40fd3363c5e*
** The alias for this session is *0*.
For example, if the URL is http://localhost:8080/?_s=0 this alias would be used.
** This is the default session.
This means that if no session alias is specified, then this session is used.
For example, if the URL is http://localhost:8080/ this session would be used.
* There is a session with the id *1d526d4a-c462-45a4-93d9-84a39b6d44ad*
** The alias for this session is *1*.
If the session alias is *1*, then this session is used.
For example, if the URL is http://localhost:8080/?_s=1 this alias would be used.
=== Automatic Session Alias Inclusion with encodeURL
The nice thing about specifying the session alias in the URL is that we can have multiple tabs open with different active sessions.
The bad thing is that we need to include the session alias in every URL of our application.
Fortunately, Spring Session will automatically include the session alias in any URL that passes through http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#encodeURL(java.lang.String)[HttpServletResponse#encodeURL(java.lang.String)]
This means that if you are using standard tag libraries the session alias is automatically included in the URL.
For example, if we are currently using the session with the alias of *1*, then the following:
.src/main/webapp/index.jsp
[source,xml,indent=0]
----
include::{samples-dir}javaconfig/users/src/main/webapp/index.jsp[tags=link]
----
will output a link of:
[source,html]
----
<a id="navLink" href="/link.jsp?_s=1">Link</a>
----
// end::how-does-it-work[]

View File

@@ -18,7 +18,6 @@ Spring Session provides an API and implementations for managing a user's session
* <<httpsession,HttpSession>> - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way.
Additional features include:
** **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
** **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
** **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
* <<websocket,WebSocket>> - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
@@ -98,10 +97,6 @@ If you are looking to get started with Spring Session, the best place to start i
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
| link:guides/java-rest.html[REST Guide]
| {gh-samples-url}javaconfig/users[Multiple Users]
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
| link:guides/java-users.html[Multiple Users Guide]
|===
.Sample Applications using Spring XML based configuration
@@ -144,7 +139,6 @@ This means that developers can switch the `HttpSession` implementation out with
We have already mentioned that Spring Session provides transparent integration with `HttpSession`, but what benefits do we get out of this?
* **Clustered Sessions** - Spring Session makes it trivial to support <<httpsession-redis,clustered sessions>> without being tied to an application container specific solution.
* **Multiple Browser Sessions** - Spring Session supports <<httpsession-multi,managing multiple users' sessions>> in a single browser instance (i.e. multiple authenticated accounts similar to Google).
* **RESTful APIs** - Spring Session allows providing session ids in headers to work with <<httpsession-rest,RESTful APIs>>
[[httpsession-redis]]
@@ -284,17 +278,6 @@ public class SessionRepositoryFilter implements Filter {
By passing in a custom `HttpServletRequest` implementation into the `FilterChain` we ensure that anything invoked after our `Filter` uses the custom `HttpSession` implementation.
This highlights why it is important that Spring Session's `SessionRepositoryFilter` must be placed before anything that interacts with the `HttpSession`.
[[httpsession-multi]]
=== Multiple HttpSessions in Single Browser
Spring Session has the ability to support multiple sessions in a single browser instance.
This provides the ability to support authenticating with multiple users in the same browser instance (i.e. Google Accounts).
NOTE: The <<samples,Manage Multiple Users Guide>> provides a complete working example of managing multiple users in the same browser instance.
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed Manage Multiple Users Guide when integrating with your own application.
include::guides/java-users.adoc[tags=how-does-it-work,leveloffset=+1]
[[httpsession-rest]]
=== HttpSession & RESTful APIs

View File

@@ -1,5 +0,0 @@
dependencyManagement {
dependencies {
dependency 'org.webjars:bootstrap:3.3.6'
}
}

View File

@@ -1,24 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample-war'
dependencies {
compile project(':spring-session-data-redis')
compile "org.springframework:spring-web"
compile "io.lettuce:lettuce-core"
compile "org.webjars:bootstrap"
compile "org.webjars:webjars-taglib"
compile jstlDependencies
compile slf4jDependencies
compile "org.testcontainers:testcontainers"
providedCompile "javax.servlet:javax.servlet-api"
testCompile "junit:junit"
testCompile "org.springframework:spring-test"
testCompile "org.assertj:assertj-core"
integrationTestCompile seleniumDependencies
}
gretty {
jvmArgs = ['-Dspring.profiles.active=embedded-redis']
}

View File

@@ -1,236 +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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import sample.pages.HomePage;
import sample.pages.LinkPage;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class UserTests {
private static final String ROB = "rob";
private static final String USERNAME = "username";
private static final String LUKE = "luke";
private static final String NAV_LINK = "navLink";
private static final String HREF = "href";
private static final String ADD_ACCOUNT = "addAccount";
private static final String UN = "un";
private static final String LOGOUT = "logout";
private static final String ERROR = "error";
private WebDriver driver;
@Before
public void setup() {
this.driver = new HtmlUnitDriver();
}
@After
public void tearDown() {
this.driver.quit();
}
@Test
public void firstVisitNotAuthenticated() {
HomePage homePage = HomePage.go(this.driver);
homePage.assertAt();
homePage.assertUserNameEmpty();
}
@Test
public void invalidLogin() {
HomePage homePage = HomePage.go(this.driver);
String user = ROB;
homePage.login(user, user + "invalid");
WebElement errorMessage = homePage.getElementById(ERROR);
homePage.assertAt();
homePage.assertErrorInvalidAuthentication(errorMessage);
}
@Test
public void emptyUsername() {
HomePage homePage = HomePage.go(this.driver);
homePage.login("", "");
WebElement errorMessage = homePage.getElementById(ERROR);
homePage.assertAt();
homePage.assertErrorInvalidAuthentication(errorMessage);
}
@Test
public void loginSingleUser() {
loginRob();
}
@Test
public void addAccount() {
backHomeForAddLukeAccount();
}
@Test
public void logInSecondUser() {
logInLukeAccount();
}
@Test
public void followingLinksKeepsNewSession() {
followingLukeLinkSession();
}
@Test
public void switchAccountRob() {
switchAccountRobHomePage();
}
@Test
public void followingLinksKeepsOriginalSession() {
followingRobLinkSession();
}
@Test
public void switchAccountLuke() {
switchAccountLukeHomePage();
}
@Test
public void logoutLuke() {
logoutLukeAccount();
}
@Test
public void switchBackRob() {
switchBackRobHomePage();
}
@Test
public void logoutRob() {
logoutRobAccount();
}
private HomePage loginRob() {
HomePage home = HomePage.go(this.driver);
String user = ROB;
home.login(user, user);
WebElement username = home.getElementById(UN);
assertThat(username.getText()).isEqualTo(user);
return home;
}
private HomePage backHomeForAddLukeAccount() {
HomePage robHome = loginRob();
String addAccountLink = robHome.getContentAttributeByElementId(ADD_ACCOUNT, HREF);
HomePage backHome = robHome.home(this.driver, addAccountLink);
WebElement username = backHome.getElementById(USERNAME);
assertThat(username.getText()).isEmpty();
return backHome;
}
private HomePage logInLukeAccount() {
HomePage home = backHomeForAddLukeAccount();
String secondUser = LUKE;
home.login(secondUser, secondUser);
WebElement secondUserName = home.getElementById(UN);
assertThat(secondUserName.getText()).isEqualTo(secondUser);
return home;
}
private LinkPage followingLukeLinkSession() {
HomePage lukeHome = logInLukeAccount();
String navLink = lukeHome.getContentAttributeByElementId(NAV_LINK, HREF);
LinkPage lukeLinkPage = lukeHome.linkPage(this.driver, navLink);
lukeLinkPage.assertAt();
WebElement username = lukeLinkPage.getElementById(UN);
assertThat(username.getText()).isEqualTo(LUKE);
return lukeLinkPage;
}
private HomePage switchAccountRobHomePage() {
LinkPage lukeLinkPage = followingLukeLinkSession();
String robSwitch = lukeLinkPage.getSwitchElementId(ROB);
String switchLink = lukeLinkPage.getContentAttributeByElementId(robSwitch, HREF);
HomePage robHome = lukeLinkPage.home(this.driver, switchLink);
WebElement username = robHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robHome;
}
private LinkPage followingRobLinkSession() {
HomePage robHome = switchAccountRobHomePage();
String navLink = robHome.getContentAttributeByElementId(NAV_LINK, HREF);
LinkPage robLinkPage = robHome.linkPage(this.driver, navLink);
robLinkPage.assertAt();
WebElement username = robLinkPage.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robLinkPage;
}
private HomePage switchAccountLukeHomePage() {
LinkPage robLinkPage = followingRobLinkSession();
String lukeSwitch = robLinkPage.getSwitchElementId(LUKE);
String lukeSwitchLink = robLinkPage.getContentAttributeByElementId(lukeSwitch, HREF);
HomePage lukeHome = robLinkPage.home(this.driver, lukeSwitchLink);
WebElement username = lukeHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(LUKE);
return lukeHome;
}
private HomePage logoutLukeAccount() {
HomePage lukeHome = switchAccountLukeHomePage();
String logoutLink = lukeHome.getContentAttributeByElementId(LOGOUT, HREF);
HomePage home = lukeHome.home(this.driver, logoutLink);
home.assertUserNameEmpty();
return home;
}
private HomePage switchBackRobHomePage() {
HomePage homePage = logoutLukeAccount();
String robSwitch = homePage.getSwitchElementId(ROB);
String robSwitchLink = homePage.getContentAttributeByElementId(robSwitch, HREF);
HomePage robHome = homePage.home(this.driver, robSwitchLink);
WebElement username = robHome.getElementById(UN);
assertThat(username.getText()).isEqualTo(ROB);
return robHome;
}
private HomePage logoutRobAccount() {
HomePage robHome = switchBackRobHomePage();
String logoutLink = robHome.getContentAttributeByElementId(LOGOUT, HREF);
HomePage home = robHome.home(this.driver, logoutLink);
home.assertUserNameEmpty();
return home;
}
}

View File

@@ -1,74 +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.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @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:" + System.getProperty("app.port");
driver.get(baseUrl + get);
}
public static void getFrom(WebDriver driver, String resourceUrl) {
driver.get(resourceUrl);
}
public HomePage home(WebDriver driver, String resourceUrl) {
getFrom(driver, resourceUrl);
return PageFactory.initElements(driver, HomePage.class);
}
public LinkPage linkPage(WebDriver driver, String resourceUrl) {
getFrom(driver, resourceUrl);
return PageFactory.initElements(driver, LinkPage.class);
}
public String getSwitchElementId(String user) {
return "switchAccount" + user;
}
public WebElement getElementById(String id) {
return this.driver.findElement(By.id(id));
}
public String getContentAttributeByElementId(String id, String attribute) {
WebElement element = getElementById(id);
assertThat(element.getAttribute(attribute)).isNotEmpty();
return element.getAttribute(attribute);
}
}

View File

@@ -1,66 +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(name = "username")
private WebElement username;
@FindBy(name = "password")
private WebElement password;
@FindBy(css = "form[method='post']")
private WebElement form;
public HomePage(WebDriver driver) {
super(driver);
}
public static HomePage go(WebDriver driver) {
get(driver, "/");
return PageFactory.initElements(driver, HomePage.class);
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Demonstrates Multi User Log In");
}
public void assertUserNameEmpty() {
assertThat(this.username.getText()).isEmpty();
}
public void assertErrorInvalidAuthentication(WebElement errorMessage) {
assertThat(errorMessage.getText()).isEqualTo("Invalid username / password. Please ensure the username is the same as the password.");
}
public void login(String user, String password) {
this.username.sendKeys(user);
this.password.sendKeys(password);
this.form.submit();
}
}

View File

@@ -1,35 +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 static org.assertj.core.api.Assertions.assertThat;
/**
* @author Pool Dolorier
*/
public class LinkPage extends BasePage {
public LinkPage(WebDriver driver) {
super(driver);
}
public void assertAt() {
assertThat(getDriver().getTitle()).isEqualTo("Linked Page");
}
}

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 sample;
public class Account {
private String username;
private String logoutUrl;
private String switchAccountUrl;
public Account(String username, String logoutUrl, String switchAccountUrl) {
super();
this.username = username;
this.logoutUrl = logoutUrl;
this.switchAccountUrl = switchAccountUrl;
}
public String getUsername() {
return this.username;
}
public String getLogoutUrl() {
return this.logoutUrl;
}
public String getSwitchAccountUrl() {
return this.switchAccountUrl;
}
}

View File

@@ -1,39 +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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
* @author Rob Winch
*/
@Import(EmbeddedRedisConfig.class)
// tag::class[]
@Configuration
@EnableRedisHttpSession
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
// end::class[]

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;
import java.io.IOException;
import org.testcontainers.containers.GenericContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
@Profile("embedded-redis")
public class EmbeddedRedisConfig {
private static final String REDIS_DOCKER_IMAGE = "redis:4.0.2";
@Bean(initMethod = "start")
public GenericContainer redisContainer() {
return new GenericContainer(REDIS_DOCKER_IMAGE) {
@Override
public void close() {
super.close();
try {
this.dockerClient.close();
}
catch (IOException ignored) {
}
}
}.withExposedPorts(6379);
}
@Bean
@Primary
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisContainer().getContainerIpAddress(),
redisContainer().getFirstMappedPort());
}
}

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 sample;
import javax.servlet.ServletContext;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
/**
* @author Rob Winch
*/
public class Initializer extends AbstractHttpSessionApplicationInitializer {
public Initializer() {
super(Config.class);
}
@Override
protected void afterSessionRepositoryFilter(ServletContext servletContext) {
appendFilters(servletContext, new UserAccountsFilter());
}
}

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 sample;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username != null && !"".equals(username) && username.equals(password)) {
req.getSession().setAttribute("username", username);
String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url);
}
else {
String url = resp.encodeRedirectURL(req.getContextPath() + "/?error");
resp.sendRedirect(url);
}
}
private static final long serialVersionUID = -8157634860354132501L;
}

View File

@@ -1,43 +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 java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session != null) {
session.invalidate();
}
String url = resp.encodeRedirectURL(req.getContextPath() + "/");
resp.sendRedirect(url);
}
private static final long serialVersionUID = 4061762524521437433L;
}

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.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.HttpSessionManager;
public class UserAccountsFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
@SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// tag::HttpSessionManager[]
HttpSessionManager sessionManager = (HttpSessionManager) httpRequest
.getAttribute(HttpSessionManager.class.getName());
// end::HttpSessionManager[]
SessionRepository<Session> repo = (SessionRepository<Session>) httpRequest
.getAttribute(SessionRepository.class.getName());
String currentSessionAlias = sessionManager.getCurrentSessionAlias(httpRequest);
Map<String, String> sessionIds = sessionManager.getSessionIds(httpRequest);
String unauthenticatedAlias = null;
String contextPath = httpRequest.getContextPath();
List<Account> accounts = new ArrayList<>();
Account currentAccount = null;
for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String sessionId = entry.getValue();
Session session = repo.findById(sessionId);
if (session == null) {
continue;
}
String username = session.getAttribute("username");
if (username == null) {
unauthenticatedAlias = alias;
continue;
}
String logoutUrl = sessionManager.encodeURL("./logout", alias);
String switchAccountUrl = sessionManager.encodeURL("./", alias);
Account account = new Account(username, logoutUrl, switchAccountUrl);
if (currentSessionAlias.equals(alias)) {
currentAccount = account;
}
else {
accounts.add(account);
}
}
// tag::addAccountUrl[]
String addAlias = unauthenticatedAlias == null ? // <1>
sessionManager.getNewSessionAlias(httpRequest)
: // <2>
unauthenticatedAlias; // <3>
String addAccountUrl = sessionManager.encodeURL(contextPath, addAlias); // <4>
// end::addAccountUrl[]
httpRequest.setAttribute("currentAccount", currentAccount);
httpRequest.setAttribute("addAccountUrl", addAccountUrl);
httpRequest.setAttribute("accounts", accounts);
chain.doFilter(request, response);
}
public void destroy() {
}
}

View File

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

View File

@@ -1,22 +0,0 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014 Twitter, Inc.
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see http://creativecommons.org/licenses/by/3.0/.
*/
// See the Getting Started docs for more information:
// http://getbootstrap.com/getting-started/#support-ie10-width
(function () {
'use strict';
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
var msViewportStyle = document.createElement('style')
msViewportStyle.appendChild(
document.createTextNode(
'@-ms-viewport{width:auto!important}'
)
)
document.querySelector('head').appendChild(msViewportStyle)
}
})();

View File

@@ -1,112 +0,0 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demonstrates Multi User Log In</title>
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
<style type="text/css">
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<!-- Static navbar -->
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="https://github.com/spring-projects/spring-session/">Spring Session</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<c:url value="/" var="homeUrl"/>
<li class="active"><a id="navHome" href="${homeUrl}">Home</a></li>
<li>
<!-- tag::link[]
-->
<c:url value="/link.jsp" var="linkUrl"/>
<a id="navLink" href="${linkUrl}">Link</a>
<!-- end::link[]
-->
</li>
</ul>
<c:if test="${currentAccount != null or not empty accounts}">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="toggle" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><c:out value="${username}"/> <span class="caret"></span></a>
<ul id="user-menu" class="dropdown-menu" role="menu">
<c:if test="${currentAccount != null}">
<li><a id="logout" href="${currentAccount.logoutUrl}">Log Out</a></li>
<li><a id="addAccount" href="${addAccountUrl}">Add Account</a></li>
</c:if>
<c:if test="${not empty accounts}">
<li class="divider"></li>
<li class="dropdown-header">Switch Account</li>
<li class="divider"></li>
</c:if>
<c:forEach items="${accounts}" var="account">
<c:set var="encodedUsername">
<c:out value="${account.username}"/>
</c:set>
<li><a id="switchAccount${encodedUsername}" href="${account.switchAccountUrl}"><c:out value="${account.username}"/></a></li>
</c:forEach>
</ul>
</li>
</ul>
</c:if>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
<h1>Description</h1>
<p>This application demonstrates how to use Spring Session to authenticate as multiple users at a time. View authenticated users in the upper right corner.</p>
<c:choose>
<c:when test="${username == null}">
<h1>Please Log In</h1>
<p>You are not currently authenticated with this session. You can authenticate with any username password combination that are equal. A few examples to try:</p>
<ul>
<li><b>Username</b> rob and <b>Password</b> rob</li>
<li><b>Username</b> luke and <b>Password</b> luke</li>
</ul>
<c:if test="${param.error != null}">
<div id="error" class="alert alert-danger">Invalid username / password. Please ensure the username is the same as the password.</div>
</c:if>
<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post">
<div class="form-group">
<label for="username">Username</label>
<input id="username" type="text" name="username"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" type="password" name="password"/>
</div>
<input type="submit" value="Login"/>
</form>
</c:when>
<c:otherwise>
<h1 id="un"><c:out value="${username}"/></h1>
<p>You are authenticated as <b><c:out value="${username}"/></b>. Observe that you can <a href="${linkUrl}">navigate links</a> and the correct session is used. Using the links in the upper right corner you can:</p>
<ul>
<li>Log Out</li>
<li>Switch Accounts</li>
<li>Add Account</li>
</ul>
</c:otherwise>
</c:choose>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<wj:locate path="jquery.min.js" relativeTo="META-INF/resources" var="jqueryLocation"/>
<script src="<c:url value="${jqueryLocation}"/>"></script>
<wj:locate path="bootstrap.min.js" relativeTo="META-INF/resources" var="bootstrapJsLocation"/>
<script src="<c:url value="${bootstrapJsLocation}"/>"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="<c:url value="/assets/js/ie10-viewport-bug-workaround.js"/>"></script>
</body>
</html>

View File

@@ -1,85 +0,0 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="wj" uri="http://www.webjars.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Linked Page</title>
<wj:locate path="bootstrap.min.css" relativeTo="META-INF/resources" var="bootstrapCssLocation"/>
<link rel="stylesheet" href="<c:url value="${bootstrapCssLocation}"/>">
<style type="text/css">
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<!-- Static navbar -->
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="https://github.com/spring-projects/spring-session/">Spring Session</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<c:url value="/" var="homeUrl"/>
<li><a id="navHome" href="${homeUrl}">Home</a></li>
<c:url value="/link.jsp" var="linkUrl"/>
<li class="active"><a id="navLink" href="${linkUrl}">Link</a></li>
</ul>
<c:if test="${currentAccount != null or not empty accounts}">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="toggle" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><c:out value="${username}"/> <span class="caret"></span></a>
<ul id="user-menu" class="dropdown-menu" role="menu">
<c:if test="${currentAccount != null}">
<li><a id="logout" href="${currentAccount.logoutUrl}">Log Out</a></li>
<li><a id="addAccount" href="${addAccountUrl}">Add Account</a></li>
</c:if>
<c:if test="${not empty accounts}">
<li class="divider"></li>
<li class="dropdown-header">Switch Account</li>
<li class="divider"></li>
</c:if>
<c:forEach items="${accounts}" var="account">
<li><a id="switchAccount${account.username}" href="${account.switchAccountUrl}"><c:out value="${account.username}"/></a></li>
</c:forEach>
</ul>
</li>
</ul>
</c:if>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
<h1>Description</h1>
<p>This page demonstrates how we keep track of the correct user session between links even when multiple tabs are open. Try opening another tab with a different user and browse. Then come back to the original tab and see the correct user is maintained.</p>
<c:choose>
<c:when test="${username == null}">
<h1>Please Log In</h1>
<p>You are not currently authenticated with this session. <a href="${homeUrl}">Log In</a></p>
</c:when>
<c:otherwise>
<h1 id="un"><c:out value="${username}"/></h1>
<p>You are authenticated as <b><c:out value="${username}"/></b>. Observe that you can <a href="${linkUrl}">navigate links</a> and the correct session is used. Using the links in the upper right corner you can:</p>
<ul>
<li>Log Out</li>
<li>Switch Accounts</li>
<li>Add Account</li>
</ul>
</c:otherwise>
</c:choose>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<wj:locate path="jquery.min.js" relativeTo="META-INF/resources" var="jqueryLocation"/>
<script src="<c:url value="${jqueryLocation}"/>"></script>
<wj:locate path="bootstrap.min.js" relativeTo="META-INF/resources" var="bootstrapJsLocation"/>
<script src="<c:url value="${bootstrapJsLocation}"/>"></script>
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="<c:url value="/assets/js/ie10-viewport-bug-workaround.js"/>"></script>
</body>
</html>

View File

@@ -42,7 +42,6 @@ import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
@@ -127,16 +126,11 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
sessionRepositoryFilter.setHttpSessionStrategy(
(MultiHttpSessionStrategy) this.httpSessionStrategy);
}
else {
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
}
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
return sessionRepositoryFilter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (ClassUtils.isPresent(

View File

@@ -16,20 +16,10 @@
package org.springframework.session.web.http;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.session.Session;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
@@ -68,222 +58,38 @@ import org.springframework.session.web.http.CookieSerializer.CookieValue;
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* <h2>Supporting Multiple Simultaneous Sessions</h2>
*
* <p>
* By default multiple sessions are also supported. Once a session is established with the
* browser, another session can be initiated by specifying a unique value for the
* {@link #setSessionAliasParamName(String)}. For example, a request to:
* </p>
*
* <pre>
* GET /messages/?_s=1416195761178 HTTP/1.1
* Host: example.com
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* </pre>
*
* Will result in the following response:
*
* <pre>
* HTTP/1.1 200 OK
* Set-Cookie: SESSION="0 f81d4fae-7dec-11d0-a765-00a0c91e6bf6 1416195761178 8a929cde-2218-4557-8d4e-82a79a37876d"; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
* </pre>
*
* <p>
* To use the original session a request without the HTTP parameter u can be made. To use
* the new session, a request with the HTTP parameter _s=1416195761178 can be used. By
* default URLs will be rewritten to include the currently selected session.
* </p>
*
* <h2>Selecting Sessions</h2>
*
* <p>
* Sessions can be managed by using the HttpSessionManager and SessionRepository. If you
* are not using Spring in the rest of your application you can obtain a reference from
* the HttpServletRequest attributes. An example is provided below:
* </p>
*
* <code>
* HttpSessionManager sessionManager =
* (HttpSessionManager) req.getAttribute(HttpSessionManager.class.getName());
* SessionRepository&lt;Session&gt; repo =
* (SessionRepository&lt;Session&gt;) req.getAttribute(SessionRepository.class.getName());
*
* String currentSessionAlias = sessionManager.getCurrentSessionAlias(req);
* Map&lt;String, String&gt; sessionIds = sessionManager.getSessionIds(req);
* String newSessionAlias = String.valueOf(System.currentTimeMillis());
*
* String contextPath = req.getContextPath();
* List&lt;Account&gt; accounts = new ArrayList&lt;&gt;();
* Account currentAccount = null; for(Map.Entry&lt;String, String&gt; entry :
* sessionIds.entrySet()) { String alias = entry.getKey(); String sessionId =
* entry.getValue();
* </code>
*
* Session session = repo.findById(sessionId); if(session == null) { continue; }
*
* String username = session.getAttribute("username"); if(username == null) {
* newSessionAlias = alias; continue; }
*
* String logoutUrl = sessionManager.encodeURL("./logout", alias); String switchAccountUrl
* = sessionManager.encodeURL("./", alias); Account account = new Account(username,
* logoutUrl, switchAccountUrl); if(currentSessionAlias.equals(alias)) { currentAccount =
* account; } else { accounts.add(account); } }
*
* req.setAttribute("currentAccount", currentAccount); req.setAttribute("addAccountUrl",
* sessionManager.encodeURL(contextPath, newSessionAlias)); req.setAttribute("accounts",
* accounts); }
*
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public final class CookieHttpSessionStrategy
implements MultiHttpSessionStrategy, HttpSessionManager {
/**
* The default delimiter for both serialization and deserialization.
*/
private static final String DEFAULT_DELIMITER = " ";
public final class CookieHttpSessionStrategy implements HttpSessionStrategy {
private static final String SESSION_IDS_WRITTEN_ATTR = CookieHttpSessionStrategy.class
.getName().concat(".SESSIONS_WRITTEN_ATTR");
static final String DEFAULT_ALIAS = "0";
static final String DEFAULT_SESSION_ALIAS_PARAM_NAME = "_s";
private static final Pattern ALIAS_PATTERN = Pattern.compile("^[\\w-]{1,50}$");
private String sessionParam = DEFAULT_SESSION_ALIAS_PARAM_NAME;
private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionStrategy.class
.getName().concat(".WRITTEN_SESSION_ID_ATTR");
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
/**
* The delimiter between a session alias and a session id when reading a cookie value.
* The default value is " ".
*/
private String deserializationDelimiter = DEFAULT_DELIMITER;
/**
* The delimiter between a session alias and a session id when writing a cookie value.
* The default is " ".
*/
private String serializationDelimiter = DEFAULT_DELIMITER;
public String getRequestedSessionId(HttpServletRequest request) {
Map<String, String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
return sessionIds.get(sessionAlias);
}
public String getCurrentSessionAlias(HttpServletRequest request) {
if (this.sessionParam == null) {
return DEFAULT_ALIAS;
}
String u = request.getParameter(this.sessionParam);
if (u == null) {
return DEFAULT_ALIAS;
}
if (!ALIAS_PATTERN.matcher(u).matches()) {
return DEFAULT_ALIAS;
}
return u;
}
public String getNewSessionAlias(HttpServletRequest request) {
Set<String> sessionAliases = getSessionIds(request).keySet();
if (sessionAliases.isEmpty()) {
return DEFAULT_ALIAS;
}
long lastAlias = Long.decode(DEFAULT_ALIAS);
for (String alias : sessionAliases) {
long selectedAlias = safeParse(alias);
if (selectedAlias > lastAlias) {
lastAlias = selectedAlias;
}
}
return Long.toHexString(lastAlias + 1);
}
private long safeParse(String hex) {
try {
return Long.decode("0x" + hex);
}
catch (NumberFormatException notNumber) {
return 0;
}
@Override
public List<String> getRequestedSessionIds(HttpServletRequest request) {
return this.cookieSerializer.readCookieValues(request);
}
@Override
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
Set<String> sessionIdsWritten = getSessionIdsWritten(request);
if (sessionIdsWritten.contains(session.getId())) {
String sessionId = session.getId();
if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
return;
}
sessionIdsWritten.add(session.getId());
Map<String, String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
String cookieValue = createSessionCookieValue(sessionIds);
request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}
@SuppressWarnings("unchecked")
private Set<String> getSessionIdsWritten(HttpServletRequest request) {
Set<String> sessionsWritten = (Set<String>) request
.getAttribute(SESSION_IDS_WRITTEN_ATTR);
if (sessionsWritten == null) {
sessionsWritten = new HashSet<>();
request.setAttribute(SESSION_IDS_WRITTEN_ATTR, sessionsWritten);
}
return sessionsWritten;
}
private String createSessionCookieValue(Map<String, String> sessionIds) {
if (sessionIds.isEmpty()) {
return "";
}
if (sessionIds.size() == 1 && sessionIds.keySet().contains(DEFAULT_ALIAS)) {
return sessionIds.values().iterator().next();
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
String alias = entry.getKey();
String id = entry.getValue();
sb.append(alias);
sb.append(this.serializationDelimiter);
sb.append(id);
sb.append(this.serializationDelimiter);
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
.writeCookieValue(new CookieValue(request, response, sessionId));
}
@Override
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
Map<String, String> sessionIds = getSessionIds(request);
String requestedAlias = getCurrentSessionAlias(request);
sessionIds.remove(requestedAlias);
String cookieValue = createSessionCookieValue(sessionIds);
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}
/**
* Sets the name of the HTTP parameter that is used to specify the session alias. If
* the value is null, then only a single session is supported per browser.
*
* @param sessionAliasParamName the name of the HTTP parameter used to specify the
* session alias. If null, then ony a single session is supported per browser.
*/
public void setSessionAliasParamName(String sessionAliasParamName) {
this.sessionParam = sessionAliasParamName;
this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
}
/**
@@ -298,151 +104,4 @@ public final class CookieHttpSessionStrategy
this.cookieSerializer = cookieSerializer;
}
/**
* Sets the delimiter between a session alias and a session id when deserializing a
* cookie. The default is " " This is useful when using
* <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> for writing the cookies
* which doesn't allow for spaces in the cookie values.
*
* @param delimiter the delimiter to set (i.e. "_ " will try a delimeter of either "_"
* or " ")
*/
public void setDeserializationDelimiter(String delimiter) {
this.deserializationDelimiter = delimiter;
}
/**
* Sets the delimiter between a session alias and a session id when deserializing a
* cookie. The default is " ". This is useful when using
* <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> for writing the cookies
* which doesn't allow for spaces in the cookie values.
*
* @param delimiter the delimiter to set (i.e. "_")
*/
public void setSerializationDelimiter(String delimiter) {
this.serializationDelimiter = delimiter;
}
public Map<String, String> getSessionIds(HttpServletRequest request) {
List<String> cookieValues = this.cookieSerializer.readCookieValues(request);
String sessionCookieValue = cookieValues.isEmpty() ? ""
: cookieValues.iterator().next();
Map<String, String> result = new LinkedHashMap<>();
StringTokenizer tokens = new StringTokenizer(sessionCookieValue,
this.deserializationDelimiter);
if (tokens.countTokens() == 1) {
result.put(DEFAULT_ALIAS, tokens.nextToken());
return result;
}
while (tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
if (!tokens.hasMoreTokens()) {
break;
}
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}
public HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response) {
request.setAttribute(HttpSessionManager.class.getName(), this);
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response) {
return new MultiSessionHttpServletResponse(response, request);
}
public String encodeURL(String url, String sessionAlias) {
String encodedSessionAlias = urlEncode(sessionAlias);
int queryStart = url.indexOf("?");
boolean isDefaultAlias = DEFAULT_ALIAS.equals(encodedSessionAlias);
if (queryStart < 0) {
return isDefaultAlias ? url
: url + "?" + this.sessionParam + "=" + encodedSessionAlias;
}
String path = url.substring(0, queryStart);
String query = url.substring(queryStart + 1, url.length());
String replacement = isDefaultAlias ? "" : "$1" + encodedSessionAlias;
query = query.replaceFirst("((^|&)" + this.sessionParam + "=)([^&]+)?",
replacement);
String sessionParamReplacement = String.format("%s=%s", this.sessionParam,
encodedSessionAlias);
if (!isDefaultAlias && !query.contains(sessionParamReplacement)
&& url.endsWith(query)) {
// no existing alias
if (!(query.endsWith("&") || query.length() == 0)) {
query += "&";
}
query += sessionParamReplacement;
}
return path + "?" + query;
}
private String urlEncode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* A {@link CookieHttpSessionStrategy} aware {@link HttpServletResponseWrapper}.
*/
class MultiSessionHttpServletResponse extends HttpServletResponseWrapper {
private final HttpServletRequest request;
MultiSessionHttpServletResponse(HttpServletResponse response,
HttpServletRequest request) {
super(response);
this.request = request;
}
private String getCurrentSessionAliasFromUrl(String url) {
String currentSessionAliasFromUrl = null;
int queryStart = url.indexOf("?");
if (queryStart >= 0) {
String query = url.substring(queryStart + 1);
Matcher matcher = Pattern
.compile(String.format("%s=([^&]+)",
CookieHttpSessionStrategy.this.sessionParam))
.matcher(query);
if (matcher.find()) {
currentSessionAliasFromUrl = matcher.group(1);
}
}
return currentSessionAliasFromUrl;
}
@Override
public String encodeRedirectURL(String url) {
String encodedUrl = super.encodeRedirectURL(url);
String currentSessionAliasFromUrl = getCurrentSessionAliasFromUrl(encodedUrl);
String alias = (currentSessionAliasFromUrl != null)
? currentSessionAliasFromUrl : getCurrentSessionAlias(this.request);
return CookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
@Override
public String encodeURL(String url) {
String encodedUrl = super.encodeURL(url);
String currentSessionAliasFromUrl = getCurrentSessionAliasFromUrl(encodedUrl);
String alias = (currentSessionAliasFromUrl != null)
? currentSessionAliasFromUrl : getCurrentSessionAlias(this.request);
return CookieHttpSessionStrategy.this.encodeURL(encodedUrl, alias);
}
}
}

View File

@@ -16,6 +16,9 @@
package org.springframework.session.web.http;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -52,21 +55,27 @@ import org.springframework.session.Session;
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public class HeaderHttpSessionStrategy implements HttpSessionStrategy {
private String headerName = "X-Auth-Token";
public String getRequestedSessionId(HttpServletRequest request) {
return request.getHeader(this.headerName);
@Override
public List<String> getRequestedSessionIds(HttpServletRequest request) {
String headerValue = request.getHeader(this.headerName);
return headerValue != null ? Collections.singletonList(headerValue)
: Collections.emptyList();
}
@Override
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
response.setHeader(this.headerName, session.getId());
}
@Override
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
response.setHeader(this.headerName, "");

View File

@@ -1,75 +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.web.http;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/**
* Allows managing a mapping of alias to the session id for having multiple active
* sessions at the same time.
*
* @author Rob Winch
* @since 1.0
*
*/
public interface HttpSessionManager {
/**
* Gets the current session's alias from the {@link HttpServletRequest}.
*
* @param request the {@link HttpServletRequest} to obtain the current session's alias
* from.
* @return the current sessions' alias. Cannot be null.
*/
String getCurrentSessionAlias(HttpServletRequest request);
/**
* Gets a mapping of the session alias to the session id from the
* {@link HttpServletRequest}.
*
* @param request the {@link HttpServletRequest} to obtain the mapping from. Cannot be
* null.
* @return a mapping of the session alias to the session id from the
* {@link HttpServletRequest}. Cannot be null.
*/
Map<String, String> getSessionIds(HttpServletRequest request);
/**
* Provides the ability to encode the URL for a given session alias.
*
* @param url the url to encode.
* @param sessionAlias the session alias to encode.
* @return the encoded URL
*/
String encodeURL(String url, String sessionAlias);
/**
* Gets a new and unique Session alias. Typically this will be called to pass into
* {@code HttpSessionManager#encodeURL(java.lang.String)}. For example:
*
* <code>
* String newAlias = httpSessionManager.getNewSessionAlias(request);
* String addAccountUrl = httpSessionManager.encodeURL("./", newAlias);
* </code>
*
* @param request the {@link HttpServletRequest} to get a new alias from
* @return Gets a new and unique Session alias.
*/
String getNewSessionAlias(HttpServletRequest request);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* 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.
@@ -16,6 +16,8 @@
package org.springframework.session.web.http;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -25,6 +27,7 @@ import org.springframework.session.Session;
* A strategy for mapping HTTP request and responses to a {@link Session}.
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
*/
public interface HttpSessionStrategy {
@@ -36,10 +39,9 @@ public interface HttpSessionStrategy {
*
* @param request the {@link javax.servlet.http.HttpServletRequest} to obtain the
* session id from. Cannot be null.
* @return the {@link javax.servlet.http.HttpServletRequest} to obtain the session id
* from.
* @return the session ids
*/
String getRequestedSessionId(HttpServletRequest request);
List<String> getRequestedSessionIds(HttpServletRequest request);
/**
* This method is invoked when a new session is created and should inform a client
@@ -76,4 +78,5 @@ public interface HttpSessionStrategy {
* the {@link org.springframework.session.Session} Cannot be null.
*/
void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);
}

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.web.http;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* Some {@link HttpSessionStrategy} may also want to further customize
* {@link HttpServletRequest} and {@link HttpServletResponse} objects. For example,
* {@link CookieHttpSessionStrategy} customizes how URL rewriting is done to select which
* session should be used in the event multiple sessions are active.
* </p>
*
* @author Rob Winch
* @since 1.0
* @see CookieHttpSessionStrategy
*/
public interface MultiHttpSessionStrategy
extends HttpSessionStrategy, RequestResponsePostProcessor {
}

View File

@@ -1,52 +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.web.http;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Allows customizing the {@link HttpServletRequest} and/or the
* {@link HttpServletResponse}.
*
* @author Rob Winch
* @since 1.0
*/
public interface RequestResponsePostProcessor {
/**
* Allows customizing the {@link HttpServletRequest}.
*
* @param request the original {@link HttpServletRequest}. Cannot be null.
* @param response the original {@link HttpServletResponse}. This is NOT the result of
* {@link #wrapResponse(HttpServletRequest, HttpServletResponse)} Cannot be null. .
* @return a non-null {@link HttpServletRequest}
*/
HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response);
/**
* Allows customizing the {@link HttpServletResponse}.
*
* @param request the original {@link HttpServletRequest}. This is NOT the result of
* {@link #wrapRequest(HttpServletRequest, HttpServletResponse)}. Cannot be null.
* @param response the original {@link HttpServletResponse}. Cannot be null.
* @return a non-null {@link HttpServletResponse}
*/
HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response);
}

View File

@@ -51,10 +51,10 @@ import org.springframework.session.SessionRepository;
*
* <ul>
* <li>The session id is looked up using
* {@link HttpSessionStrategy#getRequestedSessionId(javax.servlet.http.HttpServletRequest)}
* {@link HttpSessionStrategy#getRequestedSessionIds(javax.servlet.http.HttpServletRequest)}
* . The default is to look in a cookie named SESSION.</li>
* <li>The session id of newly created {@link org.springframework.session.Session}
* is sent to the client using
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
* to the client using
* <li>The client is notified that the session id is no longer valid with
* {@link HttpSessionStrategy#onInvalidateSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* </li>
@@ -69,10 +69,11 @@ import org.springframework.session.SessionRepository;
* @param <S> the {@link Session} type.
* @since 1.0
* @author Rob Winch
* @author Vedran Pavic
*/
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session>
extends OncePerRequestFilter {
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER");
@@ -102,7 +103,7 @@ public class SessionRepositoryFilter<S extends Session>
private ServletContext servletContext;
private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();
/**
* Creates a new instance.
@@ -123,21 +124,6 @@ public class SessionRepositoryFilter<S extends Session>
* @param httpSessionStrategy the {@link HttpSessionStrategy} to use. Cannot be null.
*/
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
if (httpSessionStrategy == null) {
throw new IllegalArgumentException("httpSessionStrategy cannot be null");
}
this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter(
httpSessionStrategy);
}
/**
* Sets the {@link MultiHttpSessionStrategy} to be used. The default is a
* {@link CookieHttpSessionStrategy}.
*
* @param httpSessionStrategy the {@link MultiHttpSessionStrategy} to use. Cannot be
* null.
*/
public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) {
if (httpSessionStrategy == null) {
throw new IllegalArgumentException("httpSessionStrategy cannot be null");
}
@@ -155,13 +141,8 @@ public class SessionRepositoryFilter<S extends Session>
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(strategyRequest, strategyResponse);
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
@@ -213,9 +194,13 @@ public class SessionRepositoryFilter<S extends Session>
*/
private final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper {
private Boolean requestedSessionIdValid;
private boolean requestedSessionInvalidated;
private final HttpServletResponse response;
private final ServletContext servletContext;
private SessionRepositoryRequestWrapper(HttpServletRequest request,
@@ -262,6 +247,7 @@ public class SessionRepositoryFilter<S extends Session>
}
}
@Override
@SuppressWarnings("unused")
public String changeSessionId() {
HttpSession session = getSession(false);
@@ -313,25 +299,24 @@ public class SessionRepositoryFilter<S extends Session>
return currentSession;
}
String requestedSessionId = getRequestedSessionId();
if (requestedSessionId != null
&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {
S session = getSession(requestedSessionId);
if (session != null) {
if (requestedSessionId != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
S session = getSession(requestedSessionId);
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(session, getServletContext());
currentSession.setNew(false);
setCurrentSession(currentSession);
return currentSession;
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
@@ -367,7 +352,10 @@ public class SessionRepositoryFilter<S extends Session>
@Override
public String getRequestedSessionId() {
return SessionRepositoryFilter.this.httpSessionStrategy
.getRequestedSessionId(this);
.getRequestedSessionIds(this).stream()
.filter(sessionId -> SessionRepositoryFilter.this.sessionRepository
.findById(sessionId) != null)
.findFirst().orElse(null);
}
/**
@@ -390,44 +378,7 @@ public class SessionRepositoryFilter<S extends Session>
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
}
}
}
/**
* A delegating implementation of {@link MultiHttpSessionStrategy}.
*/
static class MultiHttpSessionStrategyAdapter implements MultiHttpSessionStrategy {
private HttpSessionStrategy delegate;
/**
* Create a new {@link MultiHttpSessionStrategyAdapter} instance.
* @param delegate the delegate HTTP session strategy
*/
MultiHttpSessionStrategyAdapter(HttpSessionStrategy delegate) {
this.delegate = delegate;
}
public String getRequestedSessionId(HttpServletRequest request) {
return this.delegate.getRequestedSessionId(request);
}
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
this.delegate.onNewSession(session, request, response);
}
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
this.delegate.onInvalidateSession(request, response);
}
public HttpServletRequest wrapRequest(HttpServletRequest request,
HttpServletResponse response) {
return request;
}
public HttpServletResponse wrapResponse(HttpServletRequest request,
HttpServletResponse response) {
return response;
}
}
}

View File

@@ -17,8 +17,7 @@
package org.springframework.session.config.annotation.web.http;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@@ -35,8 +34,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.session.web.http.SessionRepositoryFilter;
@@ -46,6 +46,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -72,6 +73,9 @@ public class EnableSpringHttpSessionCustomCookieSerializerTests {
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
private SessionRepository sessionRepository;
@Autowired
private CookieSerializer cookieSerializer;
@@ -84,7 +88,9 @@ public class EnableSpringHttpSessionCustomCookieSerializerTests {
public void usesReadSessionIds() throws Exception {
String sessionId = "sessionId";
given(this.cookieSerializer.readCookieValues(any(HttpServletRequest.class)))
.willReturn(Arrays.asList(sessionId));
.willReturn(Collections.singletonList(sessionId));
given(this.sessionRepository.findById(anyString()))
.willReturn(new MapSession(sessionId));
this.sessionRepositoryFilter.doFilter(this.request, this.response, this.chain);
@@ -93,6 +99,8 @@ public class EnableSpringHttpSessionCustomCookieSerializerTests {
@Test
public void usesWrite() throws Exception {
given(this.sessionRepository.findById(anyString())).willReturn(new MapSession());
this.sessionRepositoryFilter.doFilter(this.request, this.response,
new MockFilterChain() {
@@ -116,8 +124,8 @@ public class EnableSpringHttpSessionCustomCookieSerializerTests {
static class Config {
@Bean
public MapSessionRepository mapSessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
public SessionRepository sessionRepository() {
return mock(SessionRepository.class);
}
@Bean

View File

@@ -1,108 +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.config.annotation.web.http;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringHttpSessionConfiguration} using a custom
* {@link MultiHttpSessionStrategy}.
*
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class EnableSpringHttpSessionCustomMultiHttpSessionStrategyTests {
@Autowired
private MockHttpServletRequest request;
@Autowired
private MockHttpServletResponse response;
private MockFilterChain chain;
@Autowired
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
@Autowired
private MultiHttpSessionStrategy strategy;
@Before
public void setup() {
this.chain = new MockFilterChain();
}
@Test
public void wrapRequestAndResponseUsed() throws Exception {
given(this.strategy.wrapRequest(any(HttpServletRequest.class),
any(HttpServletResponse.class))).willReturn(this.request);
given(this.strategy.wrapResponse(any(HttpServletRequest.class),
any(HttpServletResponse.class))).willReturn(this.response);
this.sessionRepositoryFilter.doFilter(this.request, this.response, this.chain);
verify(this.strategy).wrapRequest(any(HttpServletRequest.class),
any(HttpServletResponse.class));
verify(this.strategy).wrapResponse(any(HttpServletRequest.class),
any(HttpServletResponse.class));
}
@EnableSpringHttpSession
@Configuration
static class Config {
@Bean
public MapSessionRepository mapSessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
@Bean
public MultiHttpSessionStrategy strategy() {
return mock(MultiHttpSessionStrategy.class);
}
}
}

View File

@@ -17,10 +17,9 @@
package org.springframework.session.web.http;
import java.util.Base64;
import java.util.Map;
import java.util.Collections;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
@@ -32,7 +31,11 @@ import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CookieHttpSessionStrategy}.
*/
public class CookieHttpSessionStrategyTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@@ -51,22 +54,22 @@ public class CookieHttpSessionStrategyTests {
@Test
public void getRequestedSessionIdNull() throws Exception {
assertThat(this.strategy.getRequestedSessionId(this.request)).isNull();
assertThat(this.strategy.getRequestedSessionIds(this.request)).isEmpty();
}
@Test
public void getRequestedSessionIdNotNull() throws Exception {
setSessionCookie(this.session.getId());
assertThat(this.strategy.getRequestedSessionId(this.request))
.isEqualTo(this.session.getId());
assertThat(this.strategy.getRequestedSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
public void getRequestedSessionIdNotNullCustomCookieName() throws Exception {
setCookieName("CUSTOM");
setSessionCookie(this.session.getId());
assertThat(this.strategy.getRequestedSessionId(this.request))
.isEqualTo(this.session.getId());
assertThat(this.strategy.getRequestedSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
@@ -97,46 +100,6 @@ public class CookieHttpSessionStrategyTests {
assertThat(base64Decode(cookies[1].getValue())).isEqualTo(newSession.getId());
}
@Test
public void onNewSessionExistingSessionSameAlias() throws Exception {
Session existing = new MapSession();
setSessionCookie(existing.getId());
this.strategy.onNewSession(this.session, this.request, this.response);
assertThat(getSessionId()).isEqualTo(this.session.getId());
}
@Test
public void onNewSessionExistingSessionNewAlias() throws Exception {
Session existing = new MapSession();
setSessionCookie(existing.getId());
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onNewSession(this.session, this.request, this.response);
assertThat(getSessionId())
.isEqualTo("0 " + existing.getId() + " new " + this.session.getId());
}
@Test
public void onNewSessionExistingSessionNewAliasCustomDelimiter() throws Exception {
this.strategy.setSerializationDelimiter("_");
Session existing = new MapSession();
setSessionCookie(existing.getId());
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onNewSession(this.session, this.request, this.response);
assertThat(getSessionId())
.isEqualTo("0_" + existing.getId() + "_new_" + this.session.getId());
}
// gh-321
@Test
public void onNewSessionExplicitAlias() throws Exception {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onNewSession(this.session, this.request, this.response);
assertThat(getSessionId()).isEqualTo("new " + this.session.getId());
}
@Test
public void onNewSessionCookiePath() throws Exception {
this.request.setContextPath("/somethingunique");
@@ -177,533 +140,12 @@ public class CookieHttpSessionStrategyTests {
assertThat(getSessionId()).isEmpty();
}
@Test
public void onDeleteSessionExistingSessionSameAlias() throws Exception {
Session existing = new MapSession();
setSessionCookie("0 " + existing.getId() + " new " + this.session.getId());
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onInvalidateSession(this.request, this.response);
assertThat(getSessionId()).isEqualTo(existing.getId());
}
@Test
public void onDeleteSessionExistingSessionNewAlias() throws Exception {
Session existing = new MapSession();
setSessionCookie("0 " + existing.getId() + " new " + this.session.getId());
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onInvalidateSession(this.request, this.response);
assertThat(getSessionId()).isEqualTo(existing.getId());
}
@Test
public void encodeURLNoExistingQuery() {
assertThat(this.strategy.encodeURL("/url", "2")).isEqualTo("/url?_s=2");
}
@Test
public void encodeURLNoExistingQueryEmpty() {
assertThat(this.strategy.encodeURL("/url?", "2")).isEqualTo("/url?_s=2");
}
@Test
public void encodeURLExistingQueryNoAlias() {
assertThat(this.strategy.encodeURL("/url?a=b", "2")).isEqualTo("/url?a=b&_s=2");
}
@Test
public void encodeURLExistingQueryExistingAliasStart() {
assertThat(this.strategy.encodeURL("/url?_s=1&y=z", "2"))
.isEqualTo("/url?_s=2&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasMiddle() {
assertThat(this.strategy.encodeURL("/url?a=b&_s=1&y=z", "2"))
.isEqualTo("/url?a=b&_s=2&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasEnd() {
assertThat(this.strategy.encodeURL("/url?a=b&_s=1", "2"))
.isEqualTo("/url?a=b&_s=2");
}
//
@Test
public void encodeURLExistingQueryParamEndsWithActualParamStart() {
assertThat(this.strategy.encodeURL("/url?x_s=1&y=z", "2"))
.isEqualTo("/url?x_s=1&y=z&_s=2");
}
@Test
public void encodeURLExistingQueryParamEndsWithActualParamMiddle() {
assertThat(this.strategy.encodeURL("/url?a=b&x_s=1&y=z", "2"))
.isEqualTo("/url?a=b&x_s=1&y=z&_s=2");
}
@Test
public void encodeURLExistingQueryParamEndsWithActualParamEnd() {
assertThat(this.strategy.encodeURL("/url?a=b&x_s=1", "2"))
.isEqualTo("/url?a=b&x_s=1&_s=2");
}
//
@Test
public void encodeURLNoExistingQueryDefaultAlias() {
assertThat(this.strategy.encodeURL("/url", "0")).isEqualTo("/url");
}
@Test
public void encodeURLNoExistingQueryEmptyDefaultAlias() {
assertThat(this.strategy.encodeURL("/url?", "0")).isEqualTo("/url?");
}
@Test
public void encodeURLExistingQueryNoAliasDefaultAlias() {
assertThat(this.strategy.encodeURL("/url?a=b", "0")).isEqualTo("/url?a=b");
}
@Test
public void encodeURLExistingQueryExistingAliasStartDefaultAlias() {
// relaxed constraint as result /url?&y=z does not hurt anything (ideally should
// remove the &)
assertThat(this.strategy.encodeURL("/url?_s=1&y=z", "0"))
.doesNotContain("_s=0&_s=1");
}
@Test
public void encodeURLExistingQueryExistingAliasMiddleDefaultAlias() {
assertThat(this.strategy.encodeURL("/url?a=b&_s=1&y=z", "0"))
.isEqualTo("/url?a=b&y=z");
}
@Test
public void encodeURLExistingQueryExistingAliasEndDefaultAlias() {
assertThat(this.strategy.encodeURL("/url?a=b&_s=1", "0")).isEqualTo("/url?a=b");
}
@Test
public void encodeURLWithSameAlias() {
String url = String.format("/url?%s=1",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
assertThat(this.strategy.encodeURL(url, "1")).isEqualTo(url);
}
@Test
public void encodeURLWithSameAliasOtherQueryParamsBefore() {
String url = String.format("/url?a=b&%s=1",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
assertThat(this.strategy.encodeURL(url, "1")).isEqualTo(url);
}
@Test
public void encodeURLWithSameAliasOtherQueryParamsAfter() {
String url = String.format("/url?%s=1&a=b",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
assertThat(this.strategy.encodeURL(url, "1")).isEqualTo(url);
}
@Test
public void encodeURLWithSameAliasOtherQueryParamsBeforeAndAfter() {
String url = String.format("/url?a=b&%s=1&c=d",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
assertThat(this.strategy.encodeURL(url, "1")).isEqualTo(url);
}
@Test
public void encodeURLMaliciousAlias() {
assertThat(this.strategy.encodeURL("/url?a=b&_s=1",
"\"> <script>alert('hi')</script>")).isEqualTo(
"/url?a=b&_s=%22%3E+%3Cscript%3Ealert%28%27hi%27%29%3C%2Fscript%3E");
}
// --- getCurrentSessionAlias
@Test
public void getCurrentSessionAliasNull() {
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasNullParamName() {
this.strategy.setSessionAliasParamName(null);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "NOT USED");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
// protect against malicious users
@Test
public void getCurrentSessionAliasContainsQuote() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "here\"this");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasContainsSingleQuote() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "here'this");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasContainsSpace() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "here this");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasContainsLt() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "here<this");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasContainsGt() {
this.strategy.setSessionAliasParamName(null);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "here>this");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getCurrentSessionAliasTooLong() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME,
"012345678901234567890123456789012345678901234567890");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
// We want some sort of length restrictions, but want to ensure some sort of length
// Technically no hard limit, but chose 50
@Test
public void getCurrentSessionAliasAllows50() {
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME,
"01234567890123456789012345678901234567890123456789");
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo("01234567890123456789012345678901234567890123456789");
}
@Test
public void getCurrentSession() {
String expectedAlias = "1";
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME,
expectedAlias);
assertThat(this.strategy.getCurrentSessionAlias(this.request))
.isEqualTo(expectedAlias);
}
// --- getNewSessionAlias
@Test
public void getNewSessionAliasNoSessions() {
assertThat(this.strategy.getNewSessionAlias(this.request))
.isEqualTo(CookieHttpSessionStrategy.DEFAULT_ALIAS);
}
@Test
public void getNewSessionAliasSingleSession() {
setSessionCookie("abc");
assertThat(this.strategy.getNewSessionAlias(this.request)).isEqualTo("1");
}
@Test
public void getNewSessionAlias2Sessions() {
setCookieWithNSessions(2);
assertThat(this.strategy.getNewSessionAlias(this.request)).isEqualTo("2");
}
@Test
public void getNewSessionAlias9Sessions() {
setCookieWithNSessions(9);
assertThat(this.strategy.getNewSessionAlias(this.request))
.isEqualToIgnoringCase("9");
}
@Test
public void getNewSessionAlias10Sessions() {
setCookieWithNSessions(10);
assertThat(this.strategy.getNewSessionAlias(this.request))
.isEqualToIgnoringCase("a");
}
@Test
public void getNewSessionAlias16Sessions() {
setCookieWithNSessions(16);
assertThat(this.strategy.getNewSessionAlias(this.request))
.isEqualToIgnoringCase("10");
}
@Test
public void getNewSessionAliasInvalidAlias() {
setSessionCookie("0 1 $ b");
assertThat(this.strategy.getNewSessionAlias(this.request))
.isEqualToIgnoringCase("1");
}
// --- getSessionIds
@Test
public void getSessionIdsNone() {
assertThat(this.strategy.getSessionIds(this.request)).isEmpty();
}
@Test
public void getSessionIdsSingle() {
String expectedId = "a";
setSessionCookie(expectedId);
Map<String, String> sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(1);
assertThat(sessionIds.get("0")).isEqualTo(expectedId);
}
@Test
public void getSessionIdsMulti() {
setSessionCookie("0 a 1 b");
Map<String, String> sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(2);
assertThat(sessionIds.get("0")).isEqualTo("a");
assertThat(sessionIds.get("1")).isEqualTo("b");
}
@Test
public void getSessionIdsMultiCustomDelimeter() {
this.strategy.setDeserializationDelimiter("_");
setSessionCookie("0_a_1_b");
Map<String, String> sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(2);
assertThat(sessionIds.get("0")).isEqualTo("a");
assertThat(sessionIds.get("1")).isEqualTo("b");
}
@Test
public void getSessionIdsMultiCustomDelimeterMigration() {
this.strategy.setDeserializationDelimiter("_ ");
this.strategy.setSerializationDelimiter("_");
// can parse the old way
setSessionCookie("0 a 1 b");
Map<String, String> sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(2);
assertThat(sessionIds.get("0")).isEqualTo("a");
assertThat(sessionIds.get("1")).isEqualTo("b");
// can parse the new way
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
setSessionCookie("0_a_1_b");
sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(2);
assertThat(sessionIds.get("0")).isEqualTo("a");
assertThat(sessionIds.get("1")).isEqualTo("b");
// writes the new way
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
Session existing = new MapSession();
setSessionCookie(existing.getId());
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "new");
this.strategy.onNewSession(this.session, this.request, this.response);
assertThat(getSessionId())
.isEqualTo("0_" + existing.getId() + "_new_" + this.session.getId());
}
@Test
public void getSessionIdsDangling() {
setSessionCookie("0 a 1 b noValue");
Map<String, String> sessionIds = this.strategy.getSessionIds(this.request);
assertThat(sessionIds.size()).isEqualTo(2);
assertThat(sessionIds.get("0")).isEqualTo("a");
assertThat(sessionIds.get("1")).isEqualTo("b");
}
// --- helper
@Test
public void createSessionCookieValue() {
assertThat(createSessionCookieValue(17)).isEqualToIgnoringCase(
"0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 a 10 b 11 c 12 d 13 e 14 f 15 10 16");
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlDoesntContainAliasCurrentReqNoAlias() {
String url = "http://www.somehost.com/some/path";
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(url);
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlDoesntContainAliasCurrentReqHasAlias() {
String url = "http://www.somehost.com/some/path";
String alias = "1";
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias);
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(String.format("%s?%s=%s", url,
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias));
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlContainsAliasCurrentReqHasNoAlias() {
String url = String.format("http://www.somehost.com/some/path?%s=5",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "4");
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(url);
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlDoesntContainAliasCurrentReqNoAliasWithOtherParams() {
String url = "http://www.somehost.com/some/path?a=b";
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(url);
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlDoesntContainAliasCurrentReqHasAliasWithOtherParams() {
String url = "http://www.somehost.com/some/path?a=b";
String alias = "1";
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias);
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(String.format("%s&%s=%s", url,
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias));
}
@Test
public void responseEncodeRedirectUrlWhereRedirectUrlContainsAliasCurrentReqHasNoAliasWithOtherParams() {
String url = String.format("http://www.somehost.com/some/path?a=b&%s=5&c=d",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "4");
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedRedirectUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedRedirectUrl).isEqualTo(url);
}
@Test
public void responseEncodeUrlWhereRedirectUrlDoesntContainAliasCurrentReqNoAlias() {
String url = "http://www.somehost.com/some/path";
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(url);
}
@Test
public void responseEncodeUrlWhereRedirectUrlDoesntContainAliasCurrentReqHasAlias() {
String url = "http://www.somehost.com/some/path";
String alias = "1";
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias);
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(String.format("%s?%s=%s", url,
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias));
}
@Test
public void responseEncodeUrlWhereRedirectUrlContainsAliasCurrentReqHasNoAlias() {
String url = String.format("http://www.somehost.com/some/path?%s=5",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "4");
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(url);
}
@Test
public void responseEncodeUrlWhereRedirectUrlDoesntContainAliasCurrentReqNoAliasWithOtherParams() {
String url = "http://www.somehost.com/some/path?a=b";
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(url);
}
@Test
public void responseEncodeUrlWhereRedirectUrlDoesntContainAliasCurrentReqHasAliasWithOtherParams() {
String url = "http://www.somehost.com/some/path?a=b";
String alias = "1";
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias);
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(String.format("%s&%s=%s", url,
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, alias));
}
@Test
public void responseEncodeUrlWhereRedirectUrlContainsAliasCurrentReqHasNoAliasWithOtherParams() {
String url = String.format("http://www.somehost.com/some/path?a=b&%s=5&c=d",
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME);
this.request.setParameter(
CookieHttpSessionStrategy.DEFAULT_SESSION_ALIAS_PARAM_NAME, "4");
HttpServletResponse wrappedResponse = this.strategy.wrapResponse(this.request,
this.response);
String encodedUrl = wrappedResponse.encodeRedirectURL(url);
assertThat(encodedUrl).isEqualTo(url);
}
private void setCookieWithNSessions(long size) {
setSessionCookie(createSessionCookieValue(size));
}
private String createSessionCookieValue(long size) {
StringBuilder sb = new StringBuilder();
@@ -720,18 +162,18 @@ public class CookieHttpSessionStrategyTests {
return sb.toString();
}
public void setCookieName(String cookieName) {
private void setCookieName(String cookieName) {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName(cookieName);
this.strategy.setCookieSerializer(cookieSerializer);
this.cookieName = cookieName;
}
public void setSessionCookie(String value) {
private void setSessionCookie(String value) {
this.request.setCookies(new Cookie(this.cookieName, base64Encode(value)));
}
public String getSessionId() {
private String getSessionId() {
return base64Decode(this.response.getCookie(this.cookieName).getValue());
}

View File

@@ -16,6 +16,8 @@
package org.springframework.session.web.http;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
@@ -26,6 +28,9 @@ import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HeaderHttpSessionStrategy}.
*/
public class HeaderSessionStrategyTests {
private MockHttpServletRequest request;
@@ -46,22 +51,22 @@ public class HeaderSessionStrategyTests {
@Test
public void getRequestedSessionIdNull() throws Exception {
assertThat(this.strategy.getRequestedSessionId(this.request)).isNull();
assertThat(this.strategy.getRequestedSessionIds(this.request)).isEmpty();
}
@Test
public void getRequestedSessionIdNotNull() throws Exception {
setSessionId(this.session.getId());
assertThat(this.strategy.getRequestedSessionId(this.request))
.isEqualTo(this.session.getId());
assertThat(this.strategy.getRequestedSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
public void getRequestedSessionIdNotNullCustomHeaderName() throws Exception {
setHeaderName("CUSTOM");
setSessionId(this.session.getId());
assertThat(this.strategy.getRequestedSessionId(this.request))
.isEqualTo(this.session.getId());
assertThat(this.strategy.getRequestedSessionIds(this.request))
.isEqualTo(Collections.singletonList(this.session.getId()));
}
@Test
@@ -116,16 +121,16 @@ public class HeaderSessionStrategyTests {
this.strategy.setHeaderName(null);
}
public void setHeaderName(String headerName) {
private void setHeaderName(String headerName) {
this.strategy.setHeaderName(headerName);
this.headerName = headerName;
}
public void setSessionId(String id) {
private void setSessionId(String id) {
this.request.addHeader(this.headerName, id);
}
public String getSessionId() {
private String getSessionId() {
return this.response.getHeader(this.headerName);
}

View File

@@ -60,12 +60,12 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
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.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -95,8 +95,7 @@ public class SessionRepositoryFilterTests {
public void setup() throws Exception {
this.sessions = new HashMap<>();
this.sessionRepository = new MapSessionRepository(this.sessions);
this.filter = new SessionRepositoryFilter<>(
this.sessionRepository);
this.filter = new SessionRepositoryFilter<>(this.sessionRepository);
setupRequest();
}
@@ -134,8 +133,7 @@ public class SessionRepositoryFilterTests {
session.setLastAccessedTime(Instant.EPOCH);
this.sessionRepository = spy(this.sessionRepository);
given(this.sessionRepository.createSession()).willReturn(session);
this.filter = new SessionRepositoryFilter<>(
this.sessionRepository);
this.filter = new SessionRepositoryFilter<>(this.sessionRepository);
doFilter(new DoInFilter() {
@Override
@@ -240,8 +238,7 @@ public class SessionRepositoryFilterTests {
@Test
public void doFilterServletContextExplicit() throws Exception {
final ServletContext expectedContext = new MockServletContext();
this.filter = new SessionRepositoryFilter<>(
this.sessionRepository);
this.filter = new SessionRepositoryFilter<>(this.sessionRepository);
this.filter.setServletContext(expectedContext);
doFilter(new DoInFilter() {
@@ -431,8 +428,7 @@ public class SessionRepositoryFilterTests {
return createSession();
}
};
this.filter = new SessionRepositoryFilter<>(
this.sessionRepository);
this.filter = new SessionRepositoryFilter<>(this.sessionRepository);
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
@@ -568,7 +564,7 @@ public class SessionRepositoryFilterTests {
ReflectionTestUtils.invokeMethod(wrappedRequest, "changeSessionId");
fail("Exected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -674,7 +670,7 @@ public class SessionRepositoryFilterTests {
sessionContext.getIds().nextElement();
fail("Expected Exception");
}
catch (NoSuchElementException success) {
catch (NoSuchElementException ignored) {
}
}
});
@@ -725,7 +721,7 @@ public class SessionRepositoryFilterTests {
session.invalidate();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -742,7 +738,7 @@ public class SessionRepositoryFilterTests {
session.getCreationTime();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -759,7 +755,7 @@ public class SessionRepositoryFilterTests {
session.getAttribute("attr");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -776,7 +772,7 @@ public class SessionRepositoryFilterTests {
session.getValue("attr");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -793,7 +789,7 @@ public class SessionRepositoryFilterTests {
session.getAttributeNames();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -810,7 +806,7 @@ public class SessionRepositoryFilterTests {
session.getValueNames();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -827,7 +823,7 @@ public class SessionRepositoryFilterTests {
session.setAttribute("a", "b");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -844,7 +840,7 @@ public class SessionRepositoryFilterTests {
session.putValue("a", "b");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -861,7 +857,7 @@ public class SessionRepositoryFilterTests {
session.removeAttribute("name");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -878,7 +874,7 @@ public class SessionRepositoryFilterTests {
session.removeValue("name");
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -895,7 +891,7 @@ public class SessionRepositoryFilterTests {
session.isNew();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -912,7 +908,7 @@ public class SessionRepositoryFilterTests {
session.getLastAccessedTime();
fail("Expected Exception");
}
catch (IllegalStateException success) {
catch (IllegalStateException ignored) {
}
}
});
@@ -1055,8 +1051,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1070,8 +1067,9 @@ public class SessionRepositoryFilterTests {
String id = wrappedRequest.getSession().getId();
wrappedResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Error");
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1084,8 +1082,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.sendRedirect("/");
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1098,8 +1097,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.flushBuffer();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1112,8 +1112,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getOutputStream().flush();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1126,8 +1127,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getOutputStream().close();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1140,8 +1142,9 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getWriter().flush();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
@@ -1154,20 +1157,28 @@ public class SessionRepositoryFilterTests {
HttpServletResponse wrappedResponse) throws IOException {
String id = wrappedRequest.getSession().getId();
wrappedResponse.getWriter().close();
assertThat(SessionRepositoryFilterTests.this.sessionRepository
.findById(id)).isNotNull();
assertThat(
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
.isNotNull();
}
});
}
// --- MultiHttpSessionStrategyAdapter
// --- HttpSessionStrategy
@Test
public void doFilterAdapterGetRequestedSessionId() throws Exception {
SessionRepository<MapSession> sessionRepository = spy(
new MapSessionRepository(new ConcurrentHashMap<>()));
this.filter = new SessionRepositoryFilter<>(sessionRepository);
this.filter.setHttpSessionStrategy(this.strategy);
final String expectedId = "MultiHttpSessionStrategyAdapter-requested-id";
given(this.strategy.getRequestedSessionId(any(HttpServletRequest.class)))
.willReturn(expectedId);
final String expectedId = "HttpSessionStrategy-requested-id";
given(this.strategy.getRequestedSessionIds(any(HttpServletRequest.class)))
.willReturn(Collections.singletonList(expectedId));
given(sessionRepository.findById(anyString()))
.willReturn(new MapSession(expectedId));
doFilter(new DoInFilter() {
@Override
@@ -1211,8 +1222,8 @@ public class SessionRepositoryFilterTests {
HttpServletRequest request = (HttpServletRequest) this.chain.getRequest();
String id = request.getSession().getId();
given(this.strategy.getRequestedSessionId(any(HttpServletRequest.class)))
.willReturn(id);
given(this.strategy.getRequestedSessionIds(any(HttpServletRequest.class)))
.willReturn(Collections.singletonList(id));
setupRequest();
doFilter(new DoInFilter() {
@@ -1243,8 +1254,8 @@ public class SessionRepositoryFilterTests {
HttpServletRequest request = (HttpServletRequest) this.chain.getRequest();
String id = request.getSession().getId();
given(this.strategy.getRequestedSessionId(any(HttpServletRequest.class)))
.willReturn(id);
given(this.strategy.getRequestedSessionIds(any(HttpServletRequest.class)))
.willReturn(Collections.singletonList(id));
doFilter(new DoInFilter() {
@Override
@@ -1340,52 +1351,7 @@ public class SessionRepositoryFilterTests {
@Test(expected = IllegalArgumentException.class)
public void setHttpSessionStrategyNull() {
this.filter.setHttpSessionStrategy((HttpSessionStrategy) null);
}
@Test(expected = IllegalArgumentException.class)
public void setMultiHttpSessionStrategyNull() {
this.filter.setHttpSessionStrategy((MultiHttpSessionStrategy) null);
}
@Test
public void getSessionFalseWithInvalidSessionIdShouldOnlyAskRepositoryOnce()
throws ServletException, IOException {
this.sessionRepository = spy(this.sessionRepository);
this.filter = new SessionRepositoryFilter<>(
this.sessionRepository);
final String nonExistantSessionId = "nonExistantSessionId";
setSessionCookie(nonExistantSessionId);
doFilter(new DoInFilter() {
@Override
public void doFilter(HttpServletRequest wrappedRequest) {
// Before first invocation
assertThat(SessionRepositoryFilterTests.this.request
.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR))
.isNull();
// First call should go all the way through to the sessioRepository (it
// will not find the session)
HttpSession session = wrappedRequest.getSession(false);
verify(SessionRepositoryFilterTests.this.sessionRepository, times(1))
.findById(nonExistantSessionId);
assertThat(session).isNull();
assertThat(SessionRepositoryFilterTests.this.request
.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR))
.isNotNull();
// Second call should not reach the sessionRepository
session = wrappedRequest.getSession(false);
verify(SessionRepositoryFilterTests.this.sessionRepository, times(1))
.findById(nonExistantSessionId); // still only called once
assertThat(session).isNull();
assertThat(SessionRepositoryFilterTests.this.request
.getAttribute(SessionRepositoryFilter.INVALID_SESSION_ID_ATTR))
.isNotNull();
}
});
this.filter.setHttpSessionStrategy(null);
}
// --- helper methods
@@ -1435,10 +1401,8 @@ public class SessionRepositoryFilterTests {
nameToCookie.put(cookie.getName(), cookie);
}
}
if (this.response.getCookies() != null) {
for (Cookie cookie : this.response.getCookies()) {
nameToCookie.put(cookie.getName(), cookie);
}
for (Cookie cookie : this.response.getCookies()) {
nameToCookie.put(cookie.getName(), cookie);
}
Cookie[] nextRequestCookies = new ArrayList<>(nameToCookie.values())
.toArray(new Cookie[0]);
@@ -1448,7 +1412,6 @@ public class SessionRepositoryFilterTests {
this.request.setCookies(nextRequestCookies);
}
@SuppressWarnings("serial")
private void doFilter(final DoInFilter doInFilter)
throws ServletException, IOException {
this.chain = new MockFilterChain(new HttpServlet() {
@@ -1456,7 +1419,7 @@ public class SessionRepositoryFilterTests {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
throws ServletException, IOException {
doInFilter.doFilter(request, response);
}
});
@@ -1471,21 +1434,25 @@ public class SessionRepositoryFilterTests {
return new String(Base64.getDecoder().decode(value));
}
abstract class DoInFilter {
private static class SessionRepositoryFilterDefaultOrder implements Ordered {
public int getOrder() {
return SessionRepositoryFilter.DEFAULT_ORDER;
}
}
private abstract class DoInFilter {
void doFilter(HttpServletRequest wrappedRequest,
HttpServletResponse wrappedResponse)
throws ServletException, IOException {
throws ServletException, IOException {
doFilter(wrappedRequest);
}
void doFilter(HttpServletRequest wrappedRequest) {
}
}
static class SessionRepositoryFilterDefaultOrder implements Ordered {
public int getOrder() {
return SessionRepositoryFilter.DEFAULT_ORDER;
}
}
}