session-management.adoc 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. [[session-mgmt]]
  2. = Authentication Persistence and Session Management
  3. Once you have got an application that is xref:servlet/authentication/index.adoc[authenticating requests], it is important to consider how that resulting authentication will be persisted and restored on future requests.
  4. This is done automatically by default, so no additional code is necessary, though there are some steps you should consider. The first is setting the `requireExplicitSave` property in `HttpSecurity`.
  5. You can do it like so:
  6. ====
  7. .Java
  8. [source,java,role="primary"]
  9. ----
  10. @Bean
  11. public SecurityFilterChain filterChain(HttpSecurity http) {
  12. http
  13. // ...
  14. .securityContext((context) -> context
  15. .requireExplicitSave(true)
  16. );
  17. return http.build();
  18. }
  19. ----
  20. .Kotlin
  21. [source,kotlin,role="secondary"]
  22. ----
  23. @Bean
  24. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  25. http {
  26. // ...
  27. securityContext {
  28. requireExplicitSave = true
  29. }
  30. }
  31. return http.build()
  32. }
  33. ----
  34. .XML
  35. [source,xml,role="secondary"]
  36. ----
  37. <http security-context-explicit-save="true">
  38. <!-- ... -->
  39. </http>
  40. ----
  41. ====
  42. The most straightforward reason for this is that it is xref:migration/servlet/session-management.adoc#_require_explicit_saving_of_securitycontextrepository[becoming the default value in 6.0], so this will make sure you are ready for that.
  43. If you like, <<how-it-works-requireexplicitsave,you can read more about what requireExplicitSave is doing>> or <<requireexplicitsave,why it's important>>. Otherwise, in most cases you are done with this section.
  44. But before you leave, consider if any of these use cases fit your application:
  45. * I want to <<understanding-session-management-components,Understand Session Management's components>>
  46. * I want to <<ns-concurrent-sessions,restrict the number of times>> a user can be logged in concurrently
  47. * I want <<store-authentication-manually,to store the authentication directly>> myself instead of Spring Security doing it for me
  48. * I am storing the authentication manually and I want <<properly-clearing-authentication,to remove it>>
  49. * I am using <<the-sessionmanagementfilter, `SessionManagementFilter`>> and I need <<moving-away-from-sessionmanagementfilter,guidance on moving away from that>>
  50. * I want to store the authentication <<customizing-where-authentication-is-stored,in something other than the session>>
  51. * I am using a <<stateless-authentication, stateless authentication>>, but <<storing-stateless-authentication-in-the-session,I'd still like to store it in the session>>
  52. * I am using `SessionCreationPolicy.NEVER` but <<never-policy-session-still-created,the application is still creating sessions>>.
  53. [[understanding-session-management-components]]
  54. == Understanding Session Management's Components
  55. The Session Management support is composed of a few components that work together to provide the functionality.
  56. Those components are, xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[the `SecurityContextHolderFilter`], xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[the `SecurityContextPersistenceFilter`] and <<the-sessionmanagementfilter,the `SessionManagementFilter`>>.
  57. [NOTE]
  58. =====
  59. In Spring Security 6, the `SecurityContextPersistenceFilter` and `SessionManagementFilter` are not set by default.
  60. In addition to that, any application should only have either `SecurityContextHolderFilter` or `SecurityContextPersistenceFilter` set, never both.
  61. =====
  62. [[the-sessionmanagementfilter]]
  63. === The `SessionManagementFilter`
  64. The `SessionManagementFilter` checks the contents of the `SecurityContextRepository` against the current contents of the `SecurityContextHolder` to determine whether a user has been authenticated during the current request, typically by a non-interactive authentication mechanism, such as pre-authentication or remember-me footnote:[
  65. Authentication by mechanisms which perform a redirect after authenticating (such as form-login) will not be detected by `SessionManagementFilter`, as the filter will not be invoked during the authenticating request.
  66. Session-management functionality has to be handled separately in these cases.
  67. ].
  68. If the repository contains a security context, the filter does nothing.
  69. If it doesn't, and the thread-local `SecurityContext` contains a (non-anonymous) `Authentication` object, the filter assumes they have been authenticated by a previous filter in the stack.
  70. It will then invoke the configured `SessionAuthenticationStrategy`.
  71. If the user is not currently authenticated, the filter will check whether an invalid session ID has been requested (because of a timeout, for example) and will invoke the configured `InvalidSessionStrategy`, if one is set.
  72. The most common behaviour is just to redirect to a fixed URL and this is encapsulated in the standard implementation `SimpleRedirectInvalidSessionStrategy`.
  73. The latter is also used when configuring an invalid session URL through the namespace, <<session-mgmt,as described earlier>>.
  74. [[moving-away-from-sessionmanagementfilter]]
  75. ==== Moving Away From `SessionManagementFilter`
  76. In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[the `SessionAuthenticationStrategy`].
  77. The problem with this is that it means that in a typical setup, the `HttpSession` must be read for every request.
  78. In Spring Security 6, the default is that authentication mechanisms themselves must invoke the `SessionAuthenticationStrategy`.
  79. This means that there is no need to detect when `Authentication` is done and thus the `HttpSession` does not need to be read for every request.
  80. To opt into the new Spring Security 6 default, the following configuration should be used.
  81. .Require Explicit `SessionAuthenticationStrategy` Invocation
  82. ====
  83. .Java
  84. [source,java,role="primary"]
  85. ----
  86. @Bean
  87. SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  88. http
  89. // ...
  90. .sessionManagement((sessions) -> sessions
  91. .requireExplicitAuthenticationStrategy(true)
  92. );
  93. return http.build();
  94. }
  95. ----
  96. .Kotlin
  97. [source,kotlin,role="secondary"]
  98. ----
  99. @Bean
  100. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  101. http {
  102. sessionManagement {
  103. requireExplicitAuthenticationStrategy = true
  104. }
  105. }
  106. return http.build()
  107. }
  108. ----
  109. .XML
  110. [source,xml,role="secondary"]
  111. ----
  112. <http>
  113. <!-- ... -->
  114. <session-management authentication-strategy-explicit-invocation="true"/>
  115. </http>
  116. ----
  117. ====
  118. ==== Things To Consider When Moving Away From `SessionManagementFilter`
  119. When `requireExplicitAuthenticationStrategy = true`, it means that the `SessionManagementFilter` will not be used, therefore, some methods from the `sessionManagement` DSL will not have any effect.
  120. |===
  121. |Method |Replacement
  122. |`sessionAuthenticationErrorUrl`
  123. |Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
  124. |`sessionAuthenticationFailureHandler`
  125. |Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
  126. |`sessionAuthenticationStrategy`
  127. |Configure an `SessionAuthenticationStrategy` in your authentication mechanism as <<moving-away-from-sessionmanagementfilter,discussed above>>
  128. |===
  129. In Spring Security 6, if you try to use any of these methods when `requireExplicitAuthenticationStrategy = true` (the default), an exception will be thrown.
  130. [[customizing-where-authentication-is-stored]]
  131. == Customizing Where the Authentication Is Stored
  132. By default, Spring Security stores the security context for you in the HTTP session. However, here are several reasons you may want to customize that:
  133. * You may want call individual setters on the `HttpSessionSecurityContextRepository` instance
  134. * You may want to store the security context in a cache or database to enable horizontal scaling
  135. First, you need to create an implementation of `SecurityContextRepository` or use an existing implementation like `HttpSessionSecurityContextRepository`, then you can set it in `HttpSecurity`.
  136. [[customizing-the-securitycontextrepository]]
  137. .Customizing the `SecurityContextRepository`
  138. ====
  139. .Java
  140. [source,java,role="primary"]
  141. ----
  142. @Bean
  143. public SecurityFilterChain filterChain(HttpSecurity http) {
  144. SecurityContextRepository repo = new MyCustomSecurityContextRepository();
  145. http
  146. // ...
  147. .securityContext((context) -> context
  148. .requireExplicitSave(true)
  149. .securityContextRepository(repo)
  150. );
  151. return http.build();
  152. }
  153. ----
  154. .Kotlin
  155. [source,kotlin,role="secondary"]
  156. ----
  157. @Bean
  158. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  159. val repo = MyCustomSecurityContextRepository()
  160. http {
  161. // ...
  162. securityContext {
  163. requireExplicitSave = true
  164. securityContextRepository = repo
  165. }
  166. }
  167. return http.build()
  168. }
  169. ----
  170. .XML
  171. [source,xml,role="secondary"]
  172. ----
  173. <http security-context-explicit-save="true" security-context-repository-ref="repo">
  174. <!-- ... -->
  175. </http>
  176. <bean name="repo" class="com.example.MyCustomSecurityContextRepository" />
  177. ----
  178. ====
  179. [NOTE]
  180. ====
  181. The above configuration sets the `SecurityContextRepository` on the `SecurityContextHolderFilter` and **participating** authentication filters, like `UsernamePasswordAuthenticationFilter`.
  182. To also set it in stateless filters, please see <<storing-stateless-authentication-in-the-session,how to customize the `SecurityContextRepository` for Stateless Authentication>>.
  183. ====
  184. If you are using a custom authentication mechanism, you might want to <<store-authentication-manually,store the `Authentication` by yourself>>.
  185. [[store-authentication-manually]]
  186. === Storing the `Authentication` manually
  187. In some cases, for example, you might be authenticating a user manually instead of relying on Spring Security filters.
  188. You can use a custom filters or a {spring-framework-reference-url}/web.html#mvc-controller[Spring MVC controller] endpoint to do that.
  189. If you want to save the authentication between requests, in the `HttpSession`, for example, you have to do so:
  190. ====
  191. .Java
  192. [source,java,role="primary"]
  193. ----
  194. private SecurityContextRepository securityContextRepository =
  195. new HttpSessionSecurityContextRepository(); <1>
  196. @PostMapping("/login")
  197. public void login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) { <2>
  198. UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
  199. loginRequest.getUsername(), loginRequest.getPassword()); <3>
  200. Authentication authentication = authenticationManager.authenticate(token); <4>
  201. SecurityContext context = securityContextHolderStrategy.createEmptyContext();
  202. context.setAuthentication(authentication); <5>
  203. securityContextHolderStrategy.setContext(context);
  204. securityContextRepository.saveContext(context, request, response); <6>
  205. }
  206. class LoginRequest {
  207. private String username;
  208. private String password;
  209. // getters and setters
  210. }
  211. ----
  212. ====
  213. <1> Add the `SecurityContextRepository` to the controller
  214. <2> Inject the `HttpServletRequest` and `HttpServletResponse` to be able to save the `SecurityContext`
  215. <3> Create an unauthenticated `UsernamePasswordAuthenticationToken` using the provided credentials
  216. <4> Call `AuthenticationManager#authenticate` to authenticate the user
  217. <5> Create a `SecurityContext` and set the `Authentication` in it
  218. <6> Save the `SecurityContext` in the `SecurityContextRepository`
  219. And that's it.
  220. If you are not sure what `securityContextHolderStrategy` is in the above example, you can read more about it in the <<use-securitycontextholderstrategy, Using `SecurityContextStrategy` section>>.
  221. [[properly-clearing-authentication]]
  222. === Properly Clearing an Authentication
  223. If you are using Spring Security's xref:servlet/authentication/logout.adoc[Logout Support] then it handles a lot of stuff for you including clearing and saving the context.
  224. But, let's say you need to manually log users out of your app. In that case, you'll need to make sure you're clearing and saving the context properly.
  225. Now, you might already be familiar with clearing the `SecurityContextHolder` by doing `SecurityContextHolderStrategy#clearContext()`.
  226. That's great, but if your app requires an xref:migration/servlet/session-management.adoc#_require_explicit_saving_of_securitycontextrepository[explicit save of the context], simply clearing it isn't enough.
  227. The reason is that it doesn't remove it from the `SecurityContextRepository`, which means the `SecurityContext` could still be available for the next requests, and we definitely don't want that.
  228. To make sure the authentication is properly cleared and saved, you can invoke {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[the `SecurityContextLogoutHandler`] which does that for us, like so:
  229. ====
  230. .Java
  231. [source,java,role="primary"]
  232. ----
  233. SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler(); <1>
  234. handler.logout(httpServletRequest, httpServletResponse, null); <2>
  235. ----
  236. ====
  237. <1> Create a new instance of `SecurityContextLogoutHandler`
  238. <2> Call the `logout` method passing in the `HttpServletRequest`, `HttpServletResponse` and a `null` authentication because it is not required for this handler.
  239. It's important to remember that clearing and saving the context is just one piece of the logout process, therefore we recommend having Spring Security take care of it.
  240. [[stateless-authentication]]
  241. === Configuring Persistence for Stateless Authentication
  242. Sometimes there is no need to create and maintain a `HttpSession` for example, to persist the authentication across requests.
  243. Some authentication mechanisms like xref:servlet/authentication/passwords/basic.adoc[HTTP Basic] are stateless and, therefore, re-authenticates the user on every request.
  244. If you do not wish to create sessions, you can use `SessionCreationPolicy.STATELESS`, like so:
  245. ====
  246. .Java
  247. [source,java,role="primary"]
  248. ----
  249. @Bean
  250. public SecurityFilterChain filterChain(HttpSecurity http) {
  251. http
  252. // ...
  253. .sessionManagement((session) -> session
  254. .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  255. );
  256. return http.build();
  257. }
  258. ----
  259. .Kotlin
  260. [source,kotlin,role="secondary"]
  261. ----
  262. @Bean
  263. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  264. http {
  265. // ...
  266. sessionManagement {
  267. sessionCreationPolicy = SessionCreationPolicy.STATELESS
  268. }
  269. }
  270. return http.build()
  271. }
  272. ----
  273. .XML
  274. [source,xml,role="secondary"]
  275. ----
  276. <http create-session="stateless">
  277. <!-- ... -->
  278. </http>
  279. ----
  280. ====
  281. The above configuration is <<customizing-where-authentication-is-stored, configuring the `SecurityContextRepository`>> to use a `NullSecurityContextRepository` and is also xref:servlet/architecture.adoc#requestcache-prevent-saved-request[preventing the request from being saved in the session].
  282. [[never-policy-session-still-created]]
  283. If you are using `SessionCreationPolicy.NEVER`, you might notice that the application is still creating a `HttpSession`.
  284. In most cases, this happens because the xref:servlet/architecture.adoc#savedrequests[request is saved in the session] for the authenticated resource to re-request after authentication is successful.
  285. To avoid that, please refer to xref:servlet/architecture.adoc#requestcache-prevent-saved-request[how to prevent the request of being saved] section.
  286. [[storing-stateless-authentication-in-the-session]]
  287. ==== Storing Stateless Authentication in the Session
  288. If, for some reason, you are using a stateless authentication mechanism, but you still want to store the authentication in the session you can use the `HttpSessionSecurityContextRepository` instead of the `NullSecurityContextRepository`.
  289. For the xref:servlet/authentication/passwords/basic.adoc[HTTP Basic], you can add xref:servlet/configuration/java.adoc#post-processing-configured-objects[a `ObjectPostProcessor`] that changes the `SecurityContextRepository` used by the `BasicAuthenticationFilter`:
  290. .Store HTTP Basic authentication in the `HttpSession`
  291. ====
  292. .Java
  293. [source,java,role="primary"]
  294. ----
  295. @Bean
  296. SecurityFilterChain web(HttpSecurity http) throws Exception {
  297. http
  298. // ...
  299. .httpBasic((basic) -> basic
  300. .addObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {
  301. @Override
  302. public <O extends BasicAuthenticationFilter> O postProcess(O filter) {
  303. filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
  304. return filter;
  305. }
  306. })
  307. );
  308. return http.build();
  309. }
  310. ----
  311. ====
  312. The above also applies to others authentication mechanisms, like xref:servlet/oauth2/resource-server/index.adoc[Bearer Token Authentication].
  313. [[requireexplicitsave]]
  314. == Understanding Require Explicit Save
  315. In Spring Security 5, the default behavior is for the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to automatically be saved to the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] using the <<securitycontextpersistencefilter, `SecurityContextPersistenceFilter`>>.
  316. Saving must be done just prior to the `HttpServletResponse` being committed and just before `SecurityContextPersistenceFilter`.
  317. 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`).
  318. 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.
  319. For these reasons, the `SecurityContextPersistenceFilter` has been deprecated to be replaced with the `SecurityContextHolderFilter`.
  320. In Spring Security 6, the default behavior is that xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[the `SecurityContextHolderFilter`] will only read the `SecurityContext` from `SecurityContextRepository` and populate it in the `SecurityContextHolder`.
  321. Users now must explicitly save the `SecurityContext` with the `SecurityContextRepository` if they want the `SecurityContext` to persist between requests.
  322. This removes ambiguity and improves performance by only requiring writing to the `SecurityContextRepository` (i.e. `HttpSession`) when it is necessary.
  323. [[how-it-works-requireexplicitsave]]
  324. === How it works
  325. In summary, when `requireExplicitSave` is `true`, Spring Security sets up xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[the `SecurityContextHolderFilter`] instead of xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[the `SecurityContextPersistenceFilter`]
  326. [[ns-concurrent-sessions]]
  327. == Configuring Concurrent Session Control
  328. If you wish to place constraints on a single user's ability to log in to your application, Spring Security supports this out of the box with the following simple additions.
  329. First, you need to add the following listener to your configuration to keep Spring Security updated about session lifecycle events:
  330. ====
  331. .Java
  332. [source,java,role="primary"]
  333. ----
  334. @Bean
  335. public HttpSessionEventPublisher httpSessionEventPublisher() {
  336. return new HttpSessionEventPublisher();
  337. }
  338. ----
  339. .Kotlin
  340. [source,kotlin,role="secondary"]
  341. ----
  342. @Bean
  343. open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
  344. return HttpSessionEventPublisher()
  345. }
  346. ----
  347. .web.xml
  348. [source,xml,role="secondary"]
  349. ----
  350. <listener>
  351. <listener-class>
  352. org.springframework.security.web.session.HttpSessionEventPublisher
  353. </listener-class>
  354. </listener>
  355. ----
  356. ====
  357. Then add the following lines to your security configuration:
  358. ====
  359. .Java
  360. [source,java,role="primary"]
  361. ----
  362. @Bean
  363. public SecurityFilterChain filterChain(HttpSecurity http) {
  364. http
  365. .sessionManagement(session -> session
  366. .maximumSessions(1)
  367. );
  368. return http.build();
  369. }
  370. ----
  371. .Kotlin
  372. [source,kotlin,role="secondary"]
  373. ----
  374. @Bean
  375. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  376. http {
  377. sessionManagement {
  378. sessionConcurrency {
  379. maximumSessions = 1
  380. }
  381. }
  382. }
  383. return http.build()
  384. }
  385. ----
  386. .XML
  387. [source,xml,role="secondary"]
  388. ----
  389. <http>
  390. ...
  391. <session-management>
  392. <concurrency-control max-sessions="1" />
  393. </session-management>
  394. </http>
  395. ----
  396. ====
  397. This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated.
  398. Using Spring Boot, you can test the above configuration scenario the following way:
  399. ====
  400. .Java
  401. [source,java,role="primary"]
  402. ----
  403. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  404. @AutoConfigureMockMvc
  405. public class MaximumSessionsTests {
  406. @Autowired
  407. private MockMvc mvc;
  408. @Test
  409. void loginOnSecondLoginThenFirstSessionTerminated() throws Exception {
  410. MvcResult mvcResult = this.mvc.perform(formLogin())
  411. .andExpect(authenticated())
  412. .andReturn();
  413. MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
  414. this.mvc.perform(get("/").session(firstLoginSession))
  415. .andExpect(authenticated());
  416. this.mvc.perform(formLogin()).andExpect(authenticated());
  417. // first session is terminated by second login
  418. this.mvc.perform(get("/").session(firstLoginSession))
  419. .andExpect(unauthenticated());
  420. }
  421. }
  422. ----
  423. ====
  424. You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions[Maximum Sessions sample].
  425. It is also common that you would prefer to prevent a second login, in which case you can use:
  426. ====
  427. .Java
  428. [source,java,role="primary"]
  429. ----
  430. @Bean
  431. public SecurityFilterChain filterChain(HttpSecurity http) {
  432. http
  433. .sessionManagement(session -> session
  434. .maximumSessions(1)
  435. .maxSessionsPreventsLogin(true)
  436. );
  437. return http.build();
  438. }
  439. ----
  440. .Kotlin
  441. [source,kotlin,role="secondary"]
  442. ----
  443. @Bean
  444. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  445. http {
  446. sessionManagement {
  447. sessionConcurrency {
  448. maximumSessions = 1
  449. maxSessionsPreventsLogin = true
  450. }
  451. }
  452. }
  453. return http.build()
  454. }
  455. ----
  456. .XML
  457. [source,xml,role="secondary"]
  458. ----
  459. <http>
  460. <session-management>
  461. <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
  462. </session-management>
  463. </http>
  464. ----
  465. ====
  466. The second login will then be rejected.
  467. By "rejected", we mean that the user will be sent to the `authentication-failure-url` if form-based login is being used.
  468. If the second authentication takes place through another non-interactive mechanism, such as "remember-me", an "unauthorized" (401) error will be sent to the client.
  469. If instead you want to use an error page, you can add the attribute `session-authentication-error-url` to the `session-management` element.
  470. Using Spring Boot, you can test the above configuration the following way:
  471. ====
  472. .Java
  473. [source,java,role="primary"]
  474. ----
  475. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  476. @AutoConfigureMockMvc
  477. public class MaximumSessionsPreventLoginTests {
  478. @Autowired
  479. private MockMvc mvc;
  480. @Test
  481. void loginOnSecondLoginThenPreventLogin() throws Exception {
  482. MvcResult mvcResult = this.mvc.perform(formLogin())
  483. .andExpect(authenticated())
  484. .andReturn();
  485. MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
  486. this.mvc.perform(get("/").session(firstLoginSession))
  487. .andExpect(authenticated());
  488. // second login is prevented
  489. this.mvc.perform(formLogin()).andExpect(unauthenticated());
  490. // first session is still valid
  491. this.mvc.perform(get("/").session(firstLoginSession))
  492. .andExpect(authenticated());
  493. }
  494. }
  495. ----
  496. ====
  497. If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly.
  498. You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions-prevent-login[Maximum Sessions Prevent Login sample].
  499. == Detecting Timeouts
  500. Sessions expire on their own, and there is nothing that needs to be done to ensure that a security context gets removed.
  501. That said, Spring Security can detect when a session has expired and take specific actions that you indicate.
  502. For example, you may want to redirect to a specific endpoint when a user makes a request with an already-expired session.
  503. This is achieved through the `invalidSessionUrl` in `HttpSecurity`:
  504. ====
  505. .Java
  506. [source,java,role="primary"]
  507. ----
  508. @Bean
  509. public SecurityFilterChain filterChain(HttpSecurity http) {
  510. http
  511. .sessionManagement(session -> session
  512. .invalidSessionUrl("/invalidSession")
  513. );
  514. return http.build();
  515. }
  516. ----
  517. .Kotlin
  518. [source,kotlin,role="secondary"]
  519. ----
  520. @Bean
  521. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  522. http {
  523. sessionManagement {
  524. invalidSessionUrl = "/invalidSession"
  525. }
  526. }
  527. return http.build()
  528. }
  529. ----
  530. .XML
  531. [source,xml,role="secondary"]
  532. ----
  533. <http>
  534. ...
  535. <session-management invalid-session-url="/invalidSession" />
  536. </http>
  537. ----
  538. ====
  539. Note that if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser.
  540. This is because the session cookie is not cleared when you invalidate the session and will be resubmitted even if the user has logged out.
  541. If that is your case, you might want to <<clearing-session-cookie-on-logout,configure logout to clear the session cookie>>.
  542. === Customizing the Invalid Session Strategy
  543. The `invalidSessionUrl` is a convenience method for setting the `InvalidSessionStrategy` using the {security-api-url}/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.html[`SimpleRedirectInvalidSessionStrategy` implementation].
  544. If you want to customize the behavior, you can implement the {security-api-url}/org/springframework/security/web/session/InvalidSessionStrategy.html[`InvalidSessionStrategy`] interface and configure it using the `invalidSessionStrategy` method:
  545. ====
  546. .Java
  547. [source,java,role="primary"]
  548. ----
  549. @Bean
  550. public SecurityFilterChain filterChain(HttpSecurity http) {
  551. http
  552. .sessionManagement(session -> session
  553. .invalidSessionStrategy(new MyCustomInvalidSessionStrategy())
  554. );
  555. return http.build();
  556. }
  557. ----
  558. .Kotlin
  559. [source,kotlin,role="secondary"]
  560. ----
  561. @Bean
  562. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  563. http {
  564. sessionManagement {
  565. invalidSessionStrategy = MyCustomInvalidSessionStrategy()
  566. }
  567. }
  568. return http.build()
  569. }
  570. ----
  571. .XML
  572. [source,xml,role="secondary"]
  573. ----
  574. <http>
  575. ...
  576. <session-management invalid-session-strategy-ref="myCustomInvalidSessionStrategy" />
  577. <bean name="myCustomInvalidSessionStrategy" class="com.example.MyCustomInvalidSessionStrategy" />
  578. </http>
  579. ----
  580. ====
  581. [[clearing-session-cookie-on-logout]]
  582. == Clearing Session Cookies on Logout
  583. You can explicitly delete the JSESSIONID cookie on logging out, for example by using the https://w3c.github.io/webappsec-clear-site-data/[`Clear-Site-Data` header] in the logout handler:
  584. ====
  585. .Java
  586. [source,java,role="primary"]
  587. ----
  588. @Bean
  589. public SecurityFilterChain filterChain(HttpSecurity http) {
  590. http
  591. .logout((logout) -> logout
  592. .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES)))
  593. );
  594. return http.build();
  595. }
  596. ----
  597. .Kotlin
  598. [source,kotlin,role="secondary"]
  599. ----
  600. @Bean
  601. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  602. http {
  603. logout {
  604. addLogoutHandler(HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(COOKIES)))
  605. }
  606. }
  607. return http.build()
  608. }
  609. ----
  610. .XML
  611. [source,xml,role="secondary"]
  612. ----
  613. <http>
  614. <logout success-handler-ref="clearSiteDataHandler" />
  615. <b:bean id="clearSiteDataHandler" class="org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler">
  616. <b:constructor-arg>
  617. <b:bean class="org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter">
  618. <b:constructor-arg>
  619. <b:list>
  620. <b:value>COOKIES</b:value>
  621. </b:list>
  622. </b:constructor-arg>
  623. </b:bean>
  624. </b:constructor-arg>
  625. </b:bean>
  626. </http>
  627. ----
  628. ====
  629. This has the advantage of being container agnostic and will work with any container that supports the `Clear-Site-Data` header.
  630. As an alternative, you can also use the following syntax in the logout handler:
  631. ====
  632. .Java
  633. [source,java,role="primary"]
  634. ----
  635. @Bean
  636. public SecurityFilterChain filterChain(HttpSecurity http) {
  637. http
  638. .logout(logout -> logout
  639. .deleteCookies("JSESSIONID")
  640. );
  641. return http.build();
  642. }
  643. ----
  644. .Kotlin
  645. [source,kotlin,role="secondary"]
  646. ----
  647. @Bean
  648. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  649. http {
  650. logout {
  651. deleteCookies("JSESSIONID")
  652. }
  653. }
  654. return http.build()
  655. }
  656. ----
  657. .XML
  658. [source,xml,role="secondary"]
  659. ----
  660. <http>
  661. <logout delete-cookies="JSESSIONID" />
  662. </http>
  663. ----
  664. ====
  665. Unfortunately, this cannot be guaranteed to work with every servlet container, so you need to test it in your environment.
  666. [NOTE]
  667. =====
  668. If you run your application behind a proxy, you may also be able to remove the session cookie by configuring the proxy server.
  669. For example, by using Apache HTTPD's `mod_headers`, the following directive deletes the `JSESSIONID` cookie by expiring it in the response to a logout request (assuming the application is deployed under the `/tutorial` path):
  670. =====
  671. ====
  672. [source,xml]
  673. ----
  674. <LocationMatch "/tutorial/logout">
  675. Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
  676. </LocationMatch>
  677. ----
  678. ====
  679. More details on the xref:servlet/exploits/headers.adoc#servlet-headers-clear-site-data[Clear Site Data] and xref:servlet/authentication/logout.adoc[Logout sections].
  680. [[ns-session-fixation]]
  681. == Understanding Session Fixation Attack Protection
  682. https://en.wikipedia.org/wiki/Session_fixation[Session fixation] attacks are a potential risk where it is possible for a malicious attacker to create a session by accessing a site, then persuade another user to log in with the same session (by sending them a link containing the session identifier as a parameter, for example).
  683. Spring Security protects against this automatically by creating a new session or otherwise changing the session ID when a user logs in.
  684. === Configuring Session Fixation Protection
  685. You can control the strategy for Session Fixation Protection by choosing between three recommended options:
  686. * `changeSessionId` - Do not create a new session.
  687. Instead, use the session fixation protection provided by the Servlet container (`HttpServletRequest#changeSessionId()`).
  688. This option is only available in Servlet 3.1 (Java EE 7) and newer containers.
  689. Specifying it in older containers will result in an exception.
  690. This is the default in Servlet 3.1 and newer containers.
  691. * `newSession` - Create a new "clean" session, without copying the existing session data (Spring Security-related attributes will still be copied).
  692. * `migrateSession` - Create a new session and copy all existing session attributes to the new session.
  693. This is the default in Servlet 3.0 or older containers.
  694. You can configure the session fixation protection by doing:
  695. ====
  696. .Java
  697. [source,java,role="primary"]
  698. ----
  699. @Bean
  700. public SecurityFilterChain filterChain(HttpSecurity http) {
  701. http
  702. .sessionManagement((session) - session
  703. .sessionFixation((sessionFixation) -> sessionFixation
  704. .newSession()
  705. )
  706. );
  707. return http.build();
  708. }
  709. ----
  710. .Kotlin
  711. [source,kotlin,role="secondary"]
  712. ----
  713. @Bean
  714. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  715. http {
  716. sessionManagement {
  717. sessionFixation {
  718. newSession()
  719. }
  720. }
  721. }
  722. return http.build()
  723. }
  724. ----
  725. .XML
  726. [source,xml,role="secondary"]
  727. ----
  728. <http>
  729. <session-management session-fixation-protection="newSession" />
  730. </http>
  731. ----
  732. ====
  733. When session fixation protection occurs, it results in a `SessionFixationProtectionEvent` being published in the application context.
  734. If you use `changeSessionId`, this protection will __also__ result in any ``jakarta.servlet.http.HttpSessionIdListener``s being notified, so use caution if your code listens for both events.
  735. You can also set the session fixation protection to `none` to disable it, but this is not recommended as it leaves your application vulnerable.
  736. [[use-securitycontextholderstrategy]]
  737. == Using `SecurityContextHolderStrategy`
  738. Consider the following block of code:
  739. ====
  740. .Java
  741. [source,java,role="primary"]
  742. ----
  743. UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
  744. loginRequest.getUsername(), loginRequest.getPassword());
  745. Authentication authentication = this.authenticationManager.authenticate(token);
  746. // ...
  747. SecurityContext context = SecurityContextHolder.createEmptyContext(); <1>
  748. context.setAuthentication(authentication); <2>
  749. SecurityContextHolder.setContext(authentication); <3>
  750. ----
  751. ====
  752. 1. Creates an empty `SecurityContext` instance by accessing the `SecurityContextHolder` statically.
  753. 2. Sets the `Authentication` object in the `SecurityContext` instance.
  754. 3. Sets the `SecurityContext` instance in the `SecurityContextHolder` statically.
  755. While the above code works fine, it can produce some undesired effects: when components access the `SecurityContext` statically through `SecurityContextHolder`, this can create race conditions when there are multiple application contexts that want to specify the `SecurityContextHolderStrategy`.
  756. This is because in `SecurityContextHolder` there is one strategy per classloader instead of one per application context.
  757. To address this, components can wire `SecurityContextHolderStrategy` from the application context.
  758. By default, they will still look up the strategy from `SecurityContextHolder`.
  759. These changes are largely internal, but they present the opportunity for applications to autowire the `SecurityContextHolderStrategy` instead of accessing the `SecurityContext` statically.
  760. To do so, you should change the code to the following:
  761. ====
  762. .Java
  763. [source,java,role="primary"]
  764. ----
  765. public class SomeClass {
  766. private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
  767. public void someMethod() {
  768. UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
  769. loginRequest.getUsername(), loginRequest.getPassword());
  770. Authentication authentication = this.authenticationManager.authenticate(token);
  771. // ...
  772. SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); <1>
  773. context.setAuthentication(authentication); <2>
  774. this.securityContextHolderStrategy.setContext(context); <3>
  775. }
  776. }
  777. ----
  778. ====
  779. 1. Creates an empty `SecurityContext` instance using the configured `SecurityContextHolderStrategy`.
  780. 2. Sets the `Authentication` object in the `SecurityContext` instance.
  781. 3. Sets the `SecurityContext` instance in the `SecurityContextHolderStrategy`.
  782. [[session-mgmt-force-session-creation]]
  783. == Forcing Eager Session Creation
  784. At times, it can be valuable to eagerly create sessions.
  785. This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
  786. ====
  787. .Java
  788. [source,java,role="primary"]
  789. ----
  790. @Bean
  791. public SecurityFilterChain filterChain(HttpSecurity http) {
  792. http
  793. .sessionManagement(session -> session
  794. .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
  795. );
  796. return http.build();
  797. }
  798. ----
  799. .Kotlin
  800. [source,kotlin,role="secondary"]
  801. ----
  802. @Bean
  803. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  804. http {
  805. sessionManagement {
  806. sessionCreationPolicy = SessionCreationPolicy.ALWAYS
  807. }
  808. }
  809. return http.build()
  810. }
  811. ----
  812. .XML
  813. [source,xml,role="secondary"]
  814. ----
  815. <http create-session="ALWAYS">
  816. </http>
  817. ----
  818. ====
  819. == What to read next
  820. - Clustered sessions with https://docs.spring.io/spring-session/reference/index.html[Spring Session]