Multi-Customer Assisted Shopping
Broadleaf's CSR feature, allows for simultaneous multi-session customer assisted shopping.
How it works
This feature leverages and builds on top of Spring Session to handle "multi-session" management.
Spring Session requires a "backing repository" in order to store all attributes across simultaneous sessions.
By default, Broadleaf provides a JDBC backing repository out-of-box that extends Spring Session's JDBC Repo
(http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-jdbc). However, you are free
to switch out this backing repo with anything Spring Session supports, for example: Redis, Mongo, HazelCast, etc...
The mechanics behind this involve using a single session to maintain multiple "session aliases".
Internally Broadleaf uses Spring Session's CookieHttpSessionStrategy
to maintain session aliases between requests. Since a single session may contain many session aliases,
every request must have a session identifier in order to maintain context between requests.
Broadleaf provides a Javascript library that will intercept all anchor, form, and ajax requests and append the appropriate alias based on the
pages current context. In order to maintain internally initiated redirects and forwards, Broadleaf also
provides a BroadleafThymeleafViewResolver
override to amend the request with any session alias context.
Requirements
By default, this feature is disabled as there are a few requirements that your application must meet in order
for "multi-session" flows to function properly. It is also recommended that a thorough test be performed on the overall site
after being enabled to ensure that correct alias context is maintained throughout a shopping flow (i.e. no JavaScript/XHR calls outside
the context of normal DOM anchors or JQuery's Ajax calls drop the current session alias).
All URLs in your site
application must be served under a single protocol, in most cases this would be HTTPS. This is due to the way cookies (auth and session fixation protection) are set and invalidated across multiple sessions. You can configure all your intercept-url's to use "HTTPS" by changing your configuration in applicationContext-security.xml
...
<sec:intercept-url pattern="/" requires-channel="https"/>
<sec:intercept-url pattern="/**" requires-channel="https"/>
...
Or you can configure all your intercept-url's to use "HTTPS" by changing your java configuration in your WebSecurityConfigurerAdapter
configuration class
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.requiresChannel()
.antMatchers("/","/**")
.requiresSecure()
.and()
...
}
By default, multi-session is only enabled for CSR Assisted Shopping
"front-office" requests. Any "back-office" CSR site functions (such as Change Order) will not work in conjunction with any already provisioned csr shopping sessions. Any request initiated from a "back-office" request will invalidate any already initiated "front-office" request and vice-versa.
Broadleaf, by default, doesn't use Spring Security's built-in session fixation protection. Broadleaf has a custom security filter "blSessionFixationProtectionFilter" that manages an encrypted secure "http only" cookie between both secure and non-secure requests to preserve session ids. This filter must be adapted to handle multi-sessions as well. A new filter must be specified - which is outlined in the installation steps below.
Installation
- The Spring Session dependency is not included by default. Include it
If you are using a JDBC backed repo to store the session attributes (the default) you must include the following
dependency in your root pom.xml
xml
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
<!-- The version is usually inherited from the boot parent -->
</dependency>
If you aren't using a JDBC backed session repo then you'll need the following
xml
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
<!-- The version is usually inherited from the boot parent -->
</dependency>
If you are using a JDBC/Database backed repo, you must make the following schema changes:
Note: the following sample SQL below uses HSQLDB syntax, if you are using another DB, please use one of the Spring Session provided schema scripts located here as a starting point: https://github.com/spring-projects/spring-session/tree/2.0.4.RELEASE/spring-session-jdbc/src/main/resources/org/springframework/session/jdbc
DROP TABLE BLC_SPRING_SESSION_ATTRIBUTES IF EXISTS; DROP TABLE BLC_SPRING_SESSION IF EXISTS; CREATE TABLE BLC_SPRING_SESSION (PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT BLC_SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)); CREATE UNIQUE INDEX BLC_SPRING_SESSION_IX1 ON BLC_SPRING_SESSION (SESSION_ID); CREATE INDEX BLC_SPRING_SESSION_IX2 ON BLC_SPRING_SESSION (EXPIRY_TIME); CREATE INDEX BLC_SPRING_SESSION_IX3 ON BLC_SPRING_SESSION (PRINCIPAL_NAME); CREATE TABLE BLC_SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES LONGVARBINARY NOT NULL, CONSTRAINT BLC_SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT BLC_SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES BLC_SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE);
Notice that to keep convention with the framework, we've prefixed the "oob spring" table names with
BLC_
and you should do the same.To enable multi-session assisted shopping in your application, you must set the following system property in
common-shared.propterties
:csr.multi.customer.session.enabled=true
If for some reason, your application is all served under
HTTP
(e.g. just in dev), you must set the following
system property:csr.multi.customer.session.https=false
Place the filter beans into the security filter chain:
Add
blCSRMultiSessionInitFilter
as the very first filter in yourblPreSecurityFilterChain
Add
blCSRMultiSessionCleanupFilter
as the very last filter in yourblPostSecurityFilterChain
If you are using spring boot, then you do not need specify filters in a filter chain, Spring Boot will register them by default.
In addition, in order to validate multiple active CSR requests in the above filters, the
CrossAppAuthCSRMultiSessionServiceImpl
is configured to generate a unique "token" for each active CSR session (i.e. this token is passed with each request via a cookie, "BLC_CSR_MULTI_AUTH_n"). This service looks at the system propertycsr.multi.customer.session.secretKey
to generate this token. This property should be set inPRODUCTION
to an appropriate secret key. By default, it is configured to use the value: "blc-default-key" if nothing is specified. This service uses "HmacSHA1" in conjunction with the secret key to create the appropriate token, which is then verified in the aboveblCSRMultiSessionInitFilter
.Make sure all intercept-urls require either all
HTTPS
or allHTTP
see requirements section above for details.Setup the session fixation protection filter:
XML Configuration:
In
site/src/main/webapp/WEB-INF/applicationContext-security.xml
:<sec:custom-filter ref="blMultiSessionFixationProtectionFilter" before="SESSION_MANAGEMENT_FILTER"/>
Java Configuration:
In the security configuration class, add a filter registration bean to prevent the filter from being automatically registered and instead register it manually before the session management filter:
@Bean public FilterRegistrationBean blMultiSessionFixationProtectionFilterFilterRegistrationBean(@Qualifier("blMultiSessionFixationProtectionFilter") BroadleafMultiSessionFixationProtectionFilter filter ) { FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; } @Configuration public class SiteSecurityConfig extends WebSecurityConfigurerAdapter { ... @Override protected void configure(HttpSecurity http) throws Exception { http ... .addFilterBefore(multiSessionFixationProtectionFilter, SessionManagementFilter.class) ... } ... }
Additionally you should remove the
SessionFixationProtectionFilter
if you have one setup since theMultiSessionFixationProtectionFilter
will delegate to the former when appropriate.Setup the jdbc http session configuration
XML Configuration:
Create a new file:
site/src/main/webapp/WEB-INF/applicationContext-session.xml
with the following contents:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="com.broadleafcommerce.enterprise.csr.web.http.BroadleafJdbcHttpSessionConfiguration"> <constructor-arg name="dataSource" ref="webDS"/> </bean> </beans>
Then add the reference to this new app context in your RootContextConfiguration (after applicationContext-filter.xml)
/WEB-INF/applicationContext-session.xml
Java Configuration:
Add this import to your servlet configuration class:
@Import(BroadleafJdbcHttpSessionConfiguration.class)
If you are not using the JDBC backed session repository or you don't want to use Broadleaf's configuration then you'll need add the
BroadleafMultiSessionCsrConfig
andSpringHttpSessionConfiguration
in the same manner as mentioned above forBroadleafJdbcHttpSessionConfiguration
Finally, in your site's
footer.html
add the following JS dependencies:<th:block th:if="${#brc.csrMode AND #props.get('csr.multi.customer.session.enabled')}"> <blc:form> <input type="hidden" name="csrSessionAlias" th:value="${#httpServletRequest.getParameter('_s') != null ? #httpServletRequest.getParameter('_s') : 0}"/> </blc:form> <blc:bundle name="csrMultiSession.js" mapping-prefix="/js/" files="lib/URI.js, csr-multi-client.js" /> </th:block>
If you are using a JDBC Backed Repo, you can initialize a spring-enabled scheduler to clean up any inactive sessions.
JdbcOperationsSessionRepository
contains an @Scheduled method to clear expired sessions. You can enable this by adding the following to yourapplicationContext-session.xml
:<!-- configure scheduler to clean up expired csr sessions --> <task:annotation-driven scheduler="myMultiCsrSessionScheduler"/> <task:scheduler id="myMultiCsrSessionScheduler" pool-size="10"/>
Things to be aware of
If your application communicates with any external integrations (e.g. Payment Gateways) that rely on re-directs as a mechanism of transferring information.
You'll also need to make sure you extend and override those configurations to take into account the new multi-session request parameter.
For example, if your payment gateway relies on a transparent redirect to complete checkout, then you would need to make sure that the application constructs the appropriate "session aware" url:
String url = BLCSystemProperty.resolveSystemProperty("gateway.mygateway.configuration.processUrl");
url = multiSessionHelperService.encodeCurrentAliasToUrl(url);
return url;