Add SpringSessionBackedReactiveSessionRegistry

Closes gh-2824
This commit is contained in:
Marcus Hert Da Coregio
2024-02-27 15:11:00 -03:00
parent 203a10024c
commit 223a90ffbb
19 changed files with 997 additions and 1 deletions

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2014-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class HelloController {
@GetMapping("/hello")
Mono<String> hello() {
return Mono.just("Hello!");
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
import org.springframework.session.ReactiveFindByIndexNameSessionRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
class IndexController {
private final ReactiveFindByIndexNameSessionRepository<?> sessionRepository;
IndexController(ReactiveFindByIndexNameSessionRepository<?> sessionRepository) {
this.sessionRepository = sessionRepository;
}
@GetMapping("/")
Mono<String> index(Model model, Authentication authentication) {
return this.sessionRepository.findByPrincipalName(authentication.getName())
.doOnNext((sessions) -> model.addAttribute("sessions", sessions.values()))
.thenReturn("index");
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2014-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.session.ReactiveSessionRegistry;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler;
import org.springframework.security.web.server.authentication.SessionLimit;
import org.springframework.session.ReactiveFindByIndexNameSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository;
import org.springframework.session.security.SpringSessionBackedReactiveSessionRegistry;
@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
// @formatter:off
return http
.authorizeExchange(exchanges -> exchanges
.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyExchange().authenticated())
.formLogin(Customizer.withDefaults())
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions((authentication) -> {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) {
return Mono.empty();
}
return Mono.just(1);
})
.maximumSessionsExceededHandler(new PreventLoginServerMaximumSessionsExceededHandler())
)
)
.build();
// @formatter:on
}
@Bean
<S extends Session> SpringSessionBackedReactiveSessionRegistry<S> sessionRegistry(
ReactiveSessionRepository<S> sessionRepository,
ReactiveFindByIndexNameSessionRepository<S> indexedSessionRepository) {
return new SpringSessionBackedReactiveSessionRegistry<>(sessionRepository, indexedSessionRepository);
}
@Bean
MapReactiveUserDetailsService reactiveUserDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails unlimited = User.withDefaultPasswordEncoder()
.username("unlimited")
.password("password")
.roles("USER", "UNLIMITED_SESSIONS")
.build();
return new MapReactiveUserDetailsService(user, unlimited);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2014-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisIndexedWebSession;
@Configuration(proxyBeanMethods = false)
@EnableRedisIndexedWebSession
public class SessionConfig {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2014-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSessionSampleBootReactiveMaxSessions {
public static void main(String[] args) {
SpringApplication.run(SpringSessionSampleBootReactiveMaxSessions.class, args);
}
}