2
0

concurrent-sessions-control.adoc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. [[reactive-concurrent-sessions-control]]
  2. = Concurrent Sessions Control
  3. Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application.
  4. When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success.
  5. More specifically, the session management DSL will add the {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[RegisterSessionServerAuthenticationSuccessHandler] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter.
  6. The following sections contains examples of how to configure Concurrent Sessions Control.
  7. * <<reactive-concurrent-sessions-control-limit,I want to limit the number of concurrent sessions a user can have>>
  8. * <<concurrent-sessions-control-custom-strategy,I want to customize the strategy used when the maximum number of sessions is exceeded>>
  9. * <<reactive-concurrent-sessions-control-specify-session-registry,I want to know how to specify a `ReactiveSessionRegistry`>>
  10. * <<concurrent-sessions-control-sample,I want to see a sample application that uses Concurrent Sessions Control>>
  11. * <<disabling-for-authentication-filters,I want to know how to disable it for some authentication filter>>
  12. [[reactive-concurrent-sessions-control-limit]]
  13. == Limiting Concurrent Sessions
  14. By default, Spring Security will allow any number of concurrent sessions for a user.
  15. To limit the number of concurrent sessions, you can use the `maximumSessions` DSL method:
  16. .Configuring one session for any user
  17. [tabs]
  18. ======
  19. Java::
  20. +
  21. [source,java,role="primary"]
  22. ----
  23. @Bean
  24. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  25. http
  26. // ...
  27. .sessionManagement((sessions) -> sessions
  28. .concurrentSessions((concurrency) -> concurrency
  29. .maximumSessions(SessionLimit.of(1))
  30. );
  31. return http.build();
  32. }
  33. @Bean
  34. ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
  35. return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
  36. }
  37. ----
  38. Kotlin::
  39. +
  40. [source,kotlin,role="secondary"]
  41. ----
  42. @Bean
  43. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  44. return http {
  45. // ...
  46. sessionManagement {
  47. sessionConcurrency {
  48. maximumSessions = SessionLimit.of(1)
  49. }
  50. }
  51. }
  52. }
  53. @Bean
  54. open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
  55. return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
  56. }
  57. ----
  58. ======
  59. The above configuration allows one session for any user.
  60. Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant:
  61. .Configuring unlimited sessions
  62. [tabs]
  63. ======
  64. Java::
  65. +
  66. [source,java,role="primary"]
  67. ----
  68. @Bean
  69. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  70. http
  71. // ...
  72. .sessionManagement((sessions) -> sessions
  73. .concurrentSessions((concurrency) -> concurrency
  74. .maximumSessions(SessionLimit.UNLIMITED))
  75. );
  76. return http.build();
  77. }
  78. @Bean
  79. ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
  80. return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
  81. }
  82. ----
  83. Kotlin::
  84. +
  85. [source,kotlin,role="secondary"]
  86. ----
  87. @Bean
  88. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  89. return http {
  90. // ...
  91. sessionManagement {
  92. sessionConcurrency {
  93. maximumSessions = SessionLimit.UNLIMITED
  94. }
  95. }
  96. }
  97. }
  98. @Bean
  99. open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
  100. return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
  101. }
  102. ----
  103. ======
  104. Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function<Authentication, Mono<Integer>>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication:
  105. .Configuring maximumSessions based on `Authentication`
  106. [tabs]
  107. ======
  108. Java::
  109. +
  110. [source,java,role="primary"]
  111. ----
  112. @Bean
  113. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  114. http
  115. // ...
  116. .sessionManagement((sessions) -> sessions
  117. .concurrentSessions((concurrency) -> concurrency
  118. .maximumSessions(maxSessions()))
  119. );
  120. return http.build();
  121. }
  122. private SessionLimit maxSessions() {
  123. return (authentication) -> {
  124. if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) {
  125. return Mono.empty(); // allow unlimited sessions for users with ROLE_UNLIMITED_SESSIONS
  126. }
  127. if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
  128. return Mono.just(2); // allow two sessions for admins
  129. }
  130. return Mono.just(1); // allow one session for every other user
  131. };
  132. }
  133. @Bean
  134. ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
  135. return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
  136. }
  137. ----
  138. Kotlin::
  139. +
  140. [source,kotlin,role="secondary"]
  141. ----
  142. @Bean
  143. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  144. return http {
  145. // ...
  146. sessionManagement {
  147. sessionConcurrency {
  148. maximumSessions = maxSessions()
  149. }
  150. }
  151. }
  152. }
  153. fun maxSessions(): SessionLimit {
  154. return { authentication ->
  155. if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) Mono.empty
  156. if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN"))) Mono.just(2)
  157. Mono.just(1)
  158. }
  159. }
  160. @Bean
  161. open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
  162. return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
  163. }
  164. ----
  165. ======
  166. When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired.
  167. If you want to change that behavior, you can <<concurrent-sessions-control-custom-strategy,customize the strategy used when the maximum number of sessions is exceeded>>.
  168. [[concurrent-sessions-control-custom-strategy]]
  169. == Handling Maximum Number of Sessions Exceeded
  170. By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
  171. Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
  172. If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler].
  173. .Configuring maximumSessionsExceededHandler
  174. [tabs]
  175. ======
  176. Java::
  177. +
  178. [source,java,role="primary"]
  179. ----
  180. @Bean
  181. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  182. http
  183. // ...
  184. .sessionManagement((sessions) -> sessions
  185. .concurrentSessions((concurrency) -> concurrency
  186. .maximumSessions(SessionLimit.of(1))
  187. .maximumSessionsExceededHandler(new PreventLoginMaximumSessionsExceededHandler())
  188. )
  189. );
  190. return http.build();
  191. }
  192. @Bean
  193. ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
  194. return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
  195. }
  196. ----
  197. Kotlin::
  198. +
  199. [source,kotlin,role="secondary"]
  200. ----
  201. @Bean
  202. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  203. return http {
  204. // ...
  205. sessionManagement {
  206. sessionConcurrency {
  207. maximumSessions = SessionLimit.of(1)
  208. maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
  209. }
  210. }
  211. }
  212. }
  213. @Bean
  214. open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
  215. return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
  216. }
  217. ----
  218. ======
  219. [[reactive-concurrent-sessions-control-specify-session-registry]]
  220. == Specifying a `ReactiveSessionRegistry`
  221. In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
  222. Typically, in a Spring WebFlux application, you will use the {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] which makes sure that the `WebSession` is invalidated whenever the `ReactiveSessionInformation` is invalidated.
  223. Spring Security ships with {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] and {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementations of `ReactiveSessionRegistry`.
  224. [NOTE]
  225. ====
  226. When creating the `WebSessionStoreReactiveSessionRegistry`, you need to provide the `WebSessionStore` that is being used by your application.
  227. If you are using Spring WebFlux, you can use the `WebSessionManager` bean (which is usually an instance of `DefaultWebSessionManager`) to get the `WebSessionStore`.
  228. ====
  229. To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
  230. .ReactiveSessionRegistry as a Bean
  231. [tabs]
  232. ======
  233. Java::
  234. +
  235. [source,java,role="primary"]
  236. ----
  237. @Bean
  238. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  239. http
  240. // ...
  241. .sessionManagement((sessions) -> sessions
  242. .concurrentSessions((concurrency) -> concurrency
  243. .maximumSessions(SessionLimit.of(1))
  244. )
  245. );
  246. return http.build();
  247. }
  248. @Bean
  249. ReactiveSessionRegistry reactiveSessionRegistry() {
  250. return new InMemoryReactiveSessionRegistry();
  251. }
  252. ----
  253. Kotlin::
  254. +
  255. [source,kotlin,role="secondary"]
  256. ----
  257. @Bean
  258. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  259. return http {
  260. // ...
  261. sessionManagement {
  262. sessionConcurrency {
  263. maximumSessions = SessionLimit.of(1)
  264. }
  265. }
  266. }
  267. }
  268. @Bean
  269. open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
  270. return InMemoryReactiveSessionRegistry()
  271. }
  272. ----
  273. ======
  274. or you can use the `sessionRegistry` DSL method:
  275. .ReactiveSessionRegistry using sessionRegistry DSL method
  276. [tabs]
  277. ======
  278. Java::
  279. +
  280. [source,java,role="primary"]
  281. ----
  282. @Bean
  283. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  284. http
  285. // ...
  286. .sessionManagement((sessions) -> sessions
  287. .concurrentSessions((concurrency) -> concurrency
  288. .maximumSessions(SessionLimit.of(1))
  289. .sessionRegistry(new InMemoryReactiveSessionRegistry())
  290. )
  291. );
  292. return http.build();
  293. }
  294. ----
  295. Kotlin::
  296. +
  297. [source,kotlin,role="secondary"]
  298. ----
  299. @Bean
  300. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  301. return http {
  302. // ...
  303. sessionManagement {
  304. sessionConcurrency {
  305. maximumSessions = SessionLimit.of(1)
  306. sessionRegistry = InMemoryReactiveSessionRegistry()
  307. }
  308. }
  309. }
  310. }
  311. ----
  312. ======
  313. [[reactive-concurrent-sessions-control-manually-invalidating-sessions]]
  314. == Invalidating Registered User's Sessions
  315. At times, it is handy to be able to invalidate all or some of a user's sessions.
  316. For example, when a user changes their password, you may want to invalidate all of their sessions so that they are forced to log in again.
  317. To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions and then invalidate them:
  318. .Using ReactiveSessionRegistry to invalidate sessions manually
  319. [tabs]
  320. ======
  321. Java::
  322. +
  323. [source,java,role="primary"]
  324. ----
  325. public class SessionControl {
  326. private final ReactiveSessionRegistry reactiveSessionRegistry;
  327. public SessionControl(ReactiveSessionRegistry reactiveSessionRegistry) {
  328. this.reactiveSessionRegistry = reactiveSessionRegistry;
  329. }
  330. public Mono<Void> invalidateSessions(String username) {
  331. return this.reactiveSessionRegistry.getAllSessions(username)
  332. .flatMap(ReactiveSessionInformation::invalidate)
  333. .then();
  334. }
  335. }
  336. ----
  337. ======
  338. [[disabling-for-authentication-filters]]
  339. == Disabling It for Some Authentication Filters
  340. By default, Concurrent Sessions Control will be configured automatically for Form Login, OAuth 2.0 Login, and HTTP Basic authentication as long as they do not specify an `ServerAuthenticationSuccessHandler` themselves.
  341. For example, the following configuration will disable Concurrent Sessions Control for Form Login:
  342. .Disabling Concurrent Sessions Control for Form Login
  343. [tabs]
  344. ======
  345. Java::
  346. +
  347. [source,java,role="primary"]
  348. ----
  349. @Bean
  350. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  351. http
  352. // ...
  353. .formLogin((login) -> login
  354. .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
  355. )
  356. .sessionManagement((sessions) -> sessions
  357. .concurrentSessions((concurrency) -> concurrency
  358. .maximumSessions(SessionLimit.of(1))
  359. )
  360. );
  361. return http.build();
  362. }
  363. ----
  364. Kotlin::
  365. +
  366. [source,kotlin,role="secondary"]
  367. ----
  368. @Bean
  369. open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  370. return http {
  371. // ...
  372. formLogin {
  373. authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/")
  374. }
  375. sessionManagement {
  376. sessionConcurrency {
  377. maximumSessions = SessionLimit.of(1)
  378. }
  379. }
  380. }
  381. }
  382. ----
  383. ======
  384. === Adding Additional Success Handlers Without Disabling Concurrent Sessions Control
  385. You can also include additional `ServerAuthenticationSuccessHandler` instances to the list of handlers used by the authentication filter without disabling Concurrent Sessions Control.
  386. To do that you can use the `authenticationSuccessHandler(Consumer<List<ServerAuthenticationSuccessHandler>>)` method:
  387. .Adding additional handlers
  388. [tabs]
  389. ======
  390. Java::
  391. +
  392. [source,java,role="primary"]
  393. ----
  394. @Bean
  395. SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
  396. http
  397. // ...
  398. .formLogin((login) -> login
  399. .authenticationSuccessHandler((handlers) -> handlers.add(new MyAuthenticationSuccessHandler()))
  400. )
  401. .sessionManagement((sessions) -> sessions
  402. .concurrentSessions((concurrency) -> concurrency
  403. .maximumSessions(SessionLimit.of(1))
  404. )
  405. );
  406. return http.build();
  407. }
  408. ----
  409. ======
  410. [[concurrent-sessions-control-sample]]
  411. == Checking a Sample Application
  412. You can check the {gh-samples-url}/reactive/webflux/java/session-management/maximum-sessions[sample application here].