6.2 to 7.0 Migration
These migration notes assumes that you are going from Broadleaf Framework 6.2 to 7.0. There might be additional migration steps necessary for your particular implementation of Broadleaf. If so, please contact our support team support@broadleafcommerce.com or let us know through our Gitter.
Overview of new features in the 7.0.0-GA release are detailed within 7.0.0-GA Release Notes.
Module Compatibility with 7.0
The version for each of the Broadleaf module is defined in broadleaf-bom
. If you are using any particular version of any modules, either remove or update it. Here is a compatibility chart listing each of the module and version that is compatible with Broadleaf Framework 7.0.
Module Name | Version |
---|---|
broadleaf-common-modules-enterprise | 5.0.x |
broadleaf-process | 3.0.x |
broadleaf-api | 4.0.x |
broadleaf-jobs-events | 4.0.x |
broadleaf-menu | 4.0.x |
broadleaf-common-presentation | 2.0.x |
broadleaf-thymeleaf-presentation | 3.0.x |
broadleaf-account-credit (giftcard) | 5.0.x |
broadleaf-enterprise-search | 4.0.x |
broadleaf-pricelist | 5.0.x |
broadleaf-enterprise | 5.0.x |
broadleaf-i18n-enterprise | 4.0.x |
broadleaf-multitenant-singleschema | 5.0.x |
broadleaf-theme | 4.0.x |
broadleaf-advanced-cms | 4.0.x |
broadleaf-advanced-inventory | 4.0x |
broadleaf-custom-field | 4.0.x |
broadleaf-oms | 4.0.x |
broadleaf-merchandising-group | 3.0.x |
broadleaf-product-type | 3.0.x |
broadleaf-import | 4.0.x |
broadleaf-importer | 3.0.x |
broadleaf-export | 3.0.x |
broadleaf-advanced-offer | 4.0.x |
broadleaf-customer-segment | 3.0.x |
broadleaf-cart-rules | 3.0.x |
broadleaf-catalog-access-policy | 3.0.x |
broadleaf-account | 4.0.x |
broadleaf-marketplace | 3.0.x |
broadleaf-quote | 3.0.x |
broadleaf-affiliate | 3.0.x |
broadleaf-contenttests | 4.0.x |
broadleaf-data-feed | 3.0.x |
broadleaf-subscription | 4.0.x |
Major feats
Updated core 3rd-party dependency baselines
- Spring 6.0
- 5.3.8 -> 6.0.13
- Spring 6.0 Migration Notes
- Spring Boot 3.1
- Spring Security 6.1
- 5.8.1 -> 6.1.5
- Spring security 6.0 Migration Notes
- Hibernate 6.2
- 5.4.33.Final -> 6.2.13.Final
- GroupId for hibernate has changed from 'org.hibernate' to 'org.hibernate.orm'
- Hibernate 6.2 Migration Notes
- Solr 9.3
- 8.11.2 -> 9.3.0
- Solr 9 Migration Notes
- Thymleaf 3.1
- 3.0.15 -> 3.1.1
- Thymleaf Migration Notes
- Spring 6.0
Upgraded base JDK from 8 to 17, which is required for Spring 6
- Thus, the minimum system requirement for Broadleaf 7.0 is no longer Java 8 but Java 17
- Thus, the minimum system requirement for Broadleaf 7.0 is no longer Java 8 but Java 17
Replaced Java EE with Jakarta EE
- The dependencies for Java EE have been replaced with Jakarta EE. If these dependencies are present in your project, make sure to update them.
- The package names should have references to
jakarta
instead ofjavax
. Import statements will have to be updated. - Here is a blog from oracle which might be useful
Dropped Libraries
- All 'javax' libraries as part of transition to Jarkarta EE.
- Commons-text
- Spring Social. More information below.
- ESAPI: Replaced with OWASP encoder and encoder-esapi. More information below.
Framework changes
Domain changes
Note: All sql statements below are for MySql. Make sure to change the sql syntax and column types for any other databases accordingly.
Broadleaf Framework
- Add column
ENABLE_DEFAULT_SKU_IN_INVENTORY
to tableBLC_PRODUCT
. Used to be woven in with propertyenable.weave.use.default.sku.inventory
ALTER TABLE BLC_PRODUCT ADD COLUMN `ENABLE_DEFAULT_SKU_IN_INVENTORY` bit(1) DEFAULT NULL;
- Add column
LONG_DESCRIPTION
to tableBLC_PRODUCT_OPTION
.
ALTER TABLE BLC_PRODUCT_OPTION ADD COLUMN `LONG_DESCRIPTION` varchar(255) DEFAULT NULL;
- Remove column
IS_FEATURED_PRODUCT
from the tableBLC_PRODUCT
.
ALTER TABLE BLC_PRODUCT DROP COLUMN `IS_FEATURED_PRODUCT`;
- Add column
Cart Rules
- Add column
QUALIFYING_MIN_TOTAL
to tableBLC_CART_RULE
. Used to be woven in with propertyenable.weave.cartrule.qualifyingMinSubTotal
ALTER TABLE `BLC_CART_RULE` ADD `QUALIFYING_MIN_TOTAL` decimal(19,5);
- Add column
Scheduled Jobs and Events
- Add column
NODE_ID
and columnLOCK_TIMESTAMP
to tableBLC_SERIAL_EVENT_LOCK
.
ALTER TABLE `BLC_SERIAL_EVENT_LOCK` ADD `NODE_ID` varchar(255); ALTER TABLE `BLC_SERIAL_EVENT_LOCK` ADD `LOCK_TIMESTAMP` datetime;
- Add column
Some deprecated classes, fields and methods were removed as part of 7.0.1-GA release. These deprecated tables and columns can be removed when using 7.0.1-GA or greater.
DROP TABLE BLC_NODE_REGISTRATION; DROP TABLE BLC_SYSTEM_EVENT_NODE_FIN; DROP TABLE BLC_SNDBX_RLLBCK; DROP TABLE BLC_SNDBX_RLLBCK_ITEM; DROP TABLE BLC_PAYMENT_LOG; ALTER TABLE BLC_WIDGET DROP COLUMN TEMPLATE_PATH; ALTER TABLE BLC_FULFILLMENT_ORDER DROP COLUMN SHIPPER_TYPE, TRACKING_NUMBER, EXPECTED_SHIP_DATE, ACTUAL_SHIP_DATE;
Creating Schema Changelog
As with all Broadleaf upgrades, it is recommended to create your own Liquibase changelog as a sanity check that all indexes, columns, tables, and constraints are there. The steps to do so are as listed
- Generate a database for the existing codebase
- Upgrade the application to Broadleaf 7.0
Set the following properties in
default-shared.properties
blPU.hibernate.hbm2ddl.auto=create blEventPU.hibernate.hbm2ddl.auto=create blSecurePU.hibernate.hbm2ddl.auto=create blCMSStorage.hibernate.hbm2ddl.auto=create
Start up and then shut down the site application after it fully starts up
Download the Liquibase command line tool here. Make sure the version is 4.3.1 or above.
Copy the database connector JAR from your
~/.m2/repository
to the download directory of Liquibase. For MySQL it will be~/.m2/repository/mysql/mysql-connector-java/5.1.46/mysql-connector-java-5.1.46.jar
Create a file named
liquibase.properties
and add the following changing the properties according to your database setup. The reference properties all pertain to the 7.0 database we created in step 4.changeLogFile: dbchangelog-6.2-to-7.0.xml driver: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/broadleaf?characterEncoding=utf8&useUnicode=true&serverTimezone=UTC username: un password: pass referenceDriver: com.mysql.jdbc.Driver referenceUrl: jdbc:mysql://localhost:3306/broadleaf_7.0?characterEncoding=utf8&useUnicode=true&serverTimezone=UTC referenceUsername: un referencePassword: pass classpath: mysql-connector-java-5.1.46.jar
Run
liquibase diffChangeLog
which will write the changelog needed to migrate the schema from your previous database to the 7.0 databaseAdd the changelog to your changelogs if you're using Liquibase for schema changes. Otherwise run
liquibase --changeLogFile=<what your changeLogFile property is set to> updateSQL > dbchangelog-6.2-7.0.sql
which will generate the SQL equivalent to the XML and output it to that SQL file which then can be used to migrate the database.
Code, Template, and Property Changes
Spring related changes
With the spring security upgrade you can face that your servlet and/or security configs are no longer valid. Please consult spring migration guides mentioned above.
Also as an example here it is some pieces from the demo configs, you might find them useful:
@EnableMethodSecurity(securedEnabled = true)
public class AdminSecurityConfig {
............
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/css/**",
"/js/**",
"/img/**",
"/fonts/**",
"/" + assetServerUrlPrefixInternal + "/**",
"/favicon.ico",
"/robots.txt"
);
}
................
@Bean(name = "blAdminAuthenticationManager")
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authenticationProvider(authenticationProvider)
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.sessionManagement(
sm -> {
sm.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::migrateSession);
sm.enableSessionUrlRewriting(false);
}
)
.securityContext((securityContext) -> securityContext
.securityContextRepository(securityContextRepository)
)
.formLogin(
form -> form
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/login")
.loginProcessingUrl("/login_admin_post")
)
.authorizeHttpRequests(
req -> {
req.requestMatchers("/global").hasRole("GLOBAL_ADMIN");
req.requestMatchers("/sendResetPassword", "/forgotUsername", "/forgotPassword", "/resetPassword", "/login").permitAll();
req.requestMatchers("/**").authenticated();
}
)
.requiresChannel(
channel -> channel
.requestMatchers("/**")
.requiresSecure()
)
.logout(
logout -> logout
.invalidateHttpSession(true)
.deleteCookies("ActiveId")
.logoutUrl("/adminLogout.htm")
.logoutSuccessHandler(logoutSuccessHandler)
)
.portMapper(
mapper -> mapper
.http(httpServerPort).mapsTo(httpsRedirectPort)
)
.addFilterBefore(adminCsrfFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new AdminContentSecurityPolicyFilter(getCSPHeader()), AdminSecurityFilter.class);
return http.build();
}
}
@EnableMethodSecurity(securedEnabled = true)
public class SiteSecurityConfig {
............
@Bean
protected SecurityContextRepository blSecurityContextRepository(){
return new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
);
}
..............
@Resource(name = "blSecurityContextRepository")
protected SecurityContextRepository securityContextRepository;
......................
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions().disable())
.sessionManagement(
sm -> {
sm.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::migrateSession);
sm.enableSessionUrlRewriting(false);
}
)
.securityContext((securityContext) -> securityContext
.securityContextRepository(securityContextRepository)
)
.formLogin(
form -> form
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/login")
.loginProcessingUrl("/login_post.htm")
)
.authorizeHttpRequests(
req -> {
req.requestMatchers("/account/**").authenticated();
req.anyRequest().permitAll();
}
)
.requiresChannel(
channel -> channel
.requestMatchers("/")
.requiresSecure()
)
.logout(
logout -> logout
.invalidateHttpSession(true)
.deleteCookies("ActiveId")
.logoutUrl("/logout")
.logoutSuccessHandler(getLogoutSuccessHandler())
)
.portMapper(
mapper -> mapper
.http(httpServerPort).mapsTo(httpsRedirectPort)
)
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(punchout2GoSessionStartFilter(), ChannelProcessingFilter.class)
.addFilterBefore(punchout2GoAuthenticationFilter(), SwitchUserFilter.class)
.rememberMe(
httpSecurityRememberMeConfigurer -> httpSecurityRememberMeConfigurer
.tokenValiditySeconds(TOKEN_VALIDITY_SECONDS)
.userDetailsService(userDetailsService)
);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/css/**",
"/fonts/**",
"/img/**",
"/js/**",
"/widget/js/**",
"/" + assetServerUrlPrefixInternal + "/**",
"/favicon.ico");
}
@Bean(name = "blAuthenticationManager")
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
@EnableMethodSecurity(securedEnabled = true)
public class ApiSecurityConfig {
private static final Log LOG = LogFactory.getLog(ApiSecurityConfig.class);
@Value("${asset.server.url.prefix.internal}")
protected String assetServerUrlPrefixInternal;
@Value("${server.port:8445}")
private int httpsRedirectPort;
@Value("${http.server.port:8082}")
protected int httpServerPort;
@Bean(name = "blAuthenticationManager")
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/swagger-ui.html",
"/api/*/swagger-ui.html",
"/swagger-ui*/**",
"/api/*/swagger-ui*/**",
"/api/*/swagger-resources/*",
//this is default url where docs are generated
"/api/*/v3/api-docs",
"/api/*/v3/api-docs.yaml",
"/v3/api-docs/**",
"/v3/api-docs.yaml",
"/api/*/api-docs.yaml",
"/api-docs.yaml"
);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(req -> req.anyRequest().permitAll())
.sessionManagement(session -> {
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
session.sessionFixation().none();
session.enableSessionUrlRewriting(false);
})
.requiresChannel(channel -> channel.anyRequest().requires(ChannelDecisionManagerImpl.ANY_CHANNEL))
.portMapper(
mapper -> mapper
.http(httpServerPort).mapsTo(httpsRedirectPort)
)
.addFilterAfter(apiCustomerStateFilter(), RememberMeAuthenticationFilter.class);
return http.build();
}
@Bean
public Filter apiCustomerStateFilter() {
RestApiCustomerStateFilter restApiCustomerStateFilter = new RestApiCustomerStateFilter();
restApiCustomerStateFilter.setExcludeUrlPatterns(Arrays.asList("/api/*/swagger*", "/api/*/swagger*/**", "/swagger*", "/swagger*/**", "/**/swagger*/**"));
return restApiCustomerStateFilter;
}
}
Extracted check of GlobalEvents to a separate scheduler
A new scheduler has been created with the logic of checking global events for completion. Added new properties for configuring the scheduler: global event check interval event.global.check.interval.seconds
and batch page size database.event.global.check.pagesize
. Property was renamed from database.event.candidate.deletion.pagesize
to database.event.deletion.pagesize
and value was changed from 50
to 100
:
event.global.check.interval.seconds=10
database.event.global.check.pagesize=50
database.event.deletion.pagesize=100
Note: For Oracle DB clients, pagesize must be less than 1000.
Thymeleaf Processors
Thymeleaf has been upgraded to '3.1'. Custom html pages now requires th:include
to be replaced with th:insert
and fragment expressions needs to be wrapped inside ~{...}
.
See more here
Some existing custom Thymeleaf processors were updated and some were removed. Now all these processors implement BroadleafVariableExpression
instead of BroadleafVariableModifierExpression
, which requires updating HTML tags.
Please check the example for AdminModuleProcessor
: before - <blc_admin:admin_module></>
, now - ${#admin_module.getAllModules()}
. We directly call the method of the processor by the variable name. Please check the documentation for the BroadleafVariableExpression
Updated Processors:
AdminModuleExpression: <blc_admin:admin_module> -> ${#admin_module.getAllModules()}
AdminUserExpression: </blc:admin_user> -> ${#admin_user.getUser()}
NamedOrderExpression: <blc:named_order> -> ${#named_order.getWishlist()}
AdminFieldBuilderExpression: <blc_admin:admin_field_builder> -> ${#admin_field_builder.getFieldWrapper()
CreditCardTypesExpression: <blc:credit_card_types> -> ${#credit_card_types.getTypes()}
RatingsExpression: <blc:ratings> -> ${#ratings.getRatings(product.id)}
ProductOptionsExpression: <blc:product_option_display> -> ${#product_option_display.getDisplayValues(item)
ProductOptionsExpression: <blc:product_options> -> ${#product_options.getData(product.id).get('skuPricing')}
ProductOptionsExpression: <blc:product_options> -> ${#product_options.getDataAddOn(product.id, addOnXrefId).get('skuPricing')}
DataDrivenEnumVariableExpression: <blc:enumeration> -> ${#enumeration.getEnumValues('PRIMARY_RETURN_REASON_TYPE')}
DataDrivenEnumVariableExpression: <blc:enumeration> -> ${#enumeration.getEnumValues('SECONDARY_RETURN_REASON_TYPE')}
Also, please check these Removed Processors list and make sure that you are not using these tags.
Removed Processors:
BreadcrumbProcessor: <blc: breadcrumbs>
ErrorsProcessor: <blc_admin:errors>
ContentProcessor: <blc:content>
ConfigVariableProcessor: <blc:config>
DataDrivenEnumerationProcessor: <blc:enumeration>
CategoriesProcessor: <blc:categories>
OnePageCheckoutProcessor: <blc:one_page_checkout>
Removed Property:
admin.form.validation.errors.hideTopLevelFieldErrors
Removed Deprecated Classes
These deprecated classes are removed and their uses have been removed.
FieldEnumeration.java
FieldEnumerationImpl.java
FieldEnumerationItem.java
FieldEnumerationItemImpl.java
LegacyCartService.java
LegacyCartServiceImpl.java
LegacyMergeCartServiceImpl.java
LegacyOrderService.java
LegacyOrderServiceImpl.java
BLCAnnotationUtils.java
BLResourceBundleMessageSource.java
ResourceBundleExtensionPoint.java
Deprecated Country and State fields removed.
In Address.java
, 'country' and 'state' has been removed. They were deprecated in favor of 'ISOCountry' and 'ISOCountrySubdivision' with enhanced support for internationalization.
Spring social removal
Spring social reached its end of life in 2019. It has been removed from the framework and BroadleafSocialRegisterController
has been refactored to use OAuth2 implementation. More information is available here
Hibernate mapping changes
After updating to the new version of Hibernate (6.x), some annotations have become deprecated and may soon be removed. So we are replacing Hibernate annotations @Table and @Index and moving to Jakarta.
Also, worth to mention that our basic way of generating an ID is @GenericGenerator, example:
@GenericGenerator(
name = "entityId",
type = IdOverrideTableGenerator.class,
parameters = {
@Parameter(name = "segment_value", value = "EntityImpl"),
@Parameter(name = "entity_name", value = "package.of.entity.EntityImpl")
}
)
Note: Do not forget to check custom entities
LOB/CLOB text mapping changes
To support different databases, we suggest using the following approach:
@lob
@JdbcType(LongVarcharJdbcType.class)
@column(name = "STRUCTURED_DATA_TEXT", length = Integer.MAX_VALUE - 1)
private String structuredDataText;
Added @JdbcType(LongVarcharJdbcType.class) because postgres will fail to read data otherwise. So if you are using mysql you can use just length attribute+LOB, or you can keep the framework approach and use @JdbcType(LongVarcharJdbcType.class).
Also consult with hibernate-doc
Hibernate sequence generator changes
In Hibernate 6 there were changes to the formulas of table sequence generator used to calculate the next value. So when migrating from BLC 6.2 to 7.0 you might need to do be aware of this.
In general, BLC framework has protection from stale sequences; it is controlled by properties:
detect.sequence.generator.inconsistencies
auto.correct.sequence.generator.inconsistencies
So if both are turned on, then on startup your sequences should be fixed automatically.
In case you don't use it, or don't want to enable it, you need to exec the following sql:
UPDATE SEQUENCE_GENERATOR SET ID_VAL=ID_VAL+100;
Here 100 is used, assuming that you didn't change the default increment size (pretty sure you didn't), which is 50.
Hibernate Dialect Changes
Instead of BroadleafXXXDialect use dialect provided by hibernate for your database. Usually dialect is set in properties files via property <persistenceUnitName>.hibernate.dialect.
Consult with the list of dialects
ESAPI replacement
Maven dependency org.owasp.esapi:esapi
is replaced with org.owasp.encoder:encoder
and org.owasp.encoder:encoder-esapi
. Some ESAPI classes are tightly coupled with 'javax' packages and no new version with support for 'jakarta' is available yet.
In general, this should not affect your project, unless you use some of the ESAPI beyond the needs of BLC framework. Otherwise, you should not notice that something has changed. All ESAPI and Antisamy config files are still in use. Here is a short list of things that changed:
- In
UrlUtil
regex is used instead ofESAPI.validator().isValidRedirectLocation
to validate url. ESAPI.encoder().encodeForHTML
is replaced withESAPIEncoder.getInstance().encodeForHTML
ESAPI.httpUtilities().addHeader
is replaced with inline code that does the same thing.
API project spring-fox to springdoc update
Spring-fox project has been abandoned and is no longer in active development. It is missing support for jakarta and new spring. Thus, Springdoc is used instead.
Now Swagger needs to use either statically defined schema or autogenerated by Springdoc. Generation is happening on the first request, so it can take some time to fulfill the first request to the Swagger.
It is controlled by the following properties
Use the following setup to enable autogeneration of schema yaml
blc.api.doc.autogeneration.enabled=true # make sure that the following properties commented out #springdoc.api-docs.enabled #springdoc.swagger-ui.url
If you want(and basically it is advised) to have statically defined schema use the following setup
springdoc.api-docs.enabled=false # This is a url for controller that will return custom yaml file with service definitions springdoc.swagger-ui.url=/api-docs.yaml # make sure that blc.api.doc.autogeneration.enabled is false or commented out
The file api-docs.yaml already exists in the api project (it was autogenerated with some BLC endpoints) and you can continue editing it, adding your endpoints etc
You also need a controller that will supply that file to Swagger.
We have this one:
/**
* This controller is a part of openapi v3 setup.
* Its goal to provide custom yaml file with service definition instead of auto-generated.
* To enable it in default.properties uncomment properties springdoc.api-docs.enabled=false
* ,springdoc.swagger-ui.url=/api-docs.yaml. Also in uncomment in RestApiMvcConfiguration bean
* definitions: springDocConfiguration, springDocConfigProperties and comment out definition for customOpenAPI
*/
@Controller
@Hidden
public class GetDocsController {
@RequestMapping(path="/api-docs.yaml", method = RequestMethod.GET)
@ResponseBody
public String getApiDocs() throws IOException {
File resource = new ClassPathResource("api-docs.yaml").getFile();
return new String(Files.readAllBytes(resource.toPath()));
}
}
By default, swagger should be accessible by /api/v1/
Also because of springdoc migration springfox annotations are no longer accessible. Use the following guide
Spring Security Changes
You probably should not be affected by this migration but if you manually login some user you should read it through
In Spring Security 5, the default behavior is for the SecurityContext to automatically be saved to the SecurityContextRepository using the SecurityContextPersistenceFilter. Saving must be done just prior to the HttpServletResponse being committed and just before SecurityContextPersistenceFilter. Unfortunately, automatic persistence of the SecurityContext can surprise users when it is done prior to the request completing (i.e. just prior to committing the HttpServletResponse). It also is complex to keep track of the state to determine if a save is necessary causing unnecessary writes to the SecurityContextRepository (i.e. HttpSession) at times.
In Spring Security 6, the default behavior is that the SecurityContextHolderFilter will only read the SecurityContext from SecurityContextRepository and populate it in the SecurityContextHolder. Users now must explicitly save the SecurityContext with the SecurityContextRepository if they want the SecurityContext to persist between requests. This removes ambiguity and improves performance by only requiring writing to the SecurityContextRepository (i.e. HttpSession) when it is necessar
SecurityContextHolder.setContext(securityContext);
should be replaced with:
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);