SpringBoot 2.7.18, responding spring-security starter. The authentication is worked well, but the concurrent max session control is not worked. I knew that I need invoke onAuthentication() by manul, but It seems that some steps are missing. My SecurityConfig:
@EnableMethodSecurity
@SpringBootConfiguration
public class SecurityConfig {
@Bean
SessionRegistry sessionRegistry()
{
return new SessionRegistryImpl();
}
@Bean
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy(SessionRegistry sessionRegistry)
{
ConcurrentSessionControlAuthenticationStrategy cs=new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
cs.setMaximumSessions(1);
return cs;
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
GSUserDetailedsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(authenticationProvider);
}
@Bean
RoleHierarchy hierarchyVoter() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_ZHUXI\n ROLE_ZHUXI > ROLE_WEIY");
return hierarchy;
}
@Bean
MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
@Bean
HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(login -> login.loginPage("/index.html"))
.authorizeRequests(requests -> requests.antMatchers("/index.html", "/login-post", "/assets/**")
.permitAll().anyRequest().authenticated())
.csrf(csrf -> csrf.ignoringAntMatchers("/login-post"));
return http.build();
}
}
My LoginController: ```@RestController public class LoginController {
private final AuthenticationManager authenticationManager;
private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
private SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
@Autowired
private ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@PostMapping("/login-post")
public void login(@RequestBody UserInfoDTO loginRequest, HttpServletRequest request, HttpServletResponse response) {
Authentication authenticationRequest = UsernamePasswordAuthenticationToken
.unauthenticated(loginRequest.getLoginName(), loginRequest.getPassword());
Authentication authenticationResponse = this.authenticationManager.authenticate(authenticationRequest);
SecurityContext context = securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResponse);
securityContextHolderStrategy.setContext(context);
securityContextRepository.saveContext(context, request, response);
concurrentSessionControlAuthenticationStrategy.onAuthentication(authenticationResponse, request, response);
}
}
**equals() of My UserDetails(GJSJUser is the class username is distinct String)**
``` @Override
public boolean equals(Object obj) {
return obj instanceof GJSJUser ? this.getUsername().equals(((GJSJUser) obj).getUsername()) : false;
}
It seems the loggin session is not added into concurrentmap in ServiceRegistry, so the sessionRegistry.getAllSessions is always empty. What's Wrong?
Thanks a lot!
Comment From: kse-music
You should be registerNewSessionfirst, you can try to do like so,
1. Add RegisterSessionAuthenticationStrategy bean
@Bean
RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy (SessionRegistry sessionRegistry) {
return new RegisterSessionAuthenticationStrategy(sessionRegistry);
}
- Use in LoginController
@Autowired
private RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy ;
Invoke registerSessionAuthenticationStrategy.onAuthentication(authenticationResponse, request, response) before invoke concurrentSessionControlAuthenticationStrategy.onAuthentication(authenticationResponse, request, response)
Comment From: NieNoRc
Thank you. It's helpful.
It can be achieved that preventing second login by set setExceptionIfMaximumExceeded(true).
But If I want achieve: disable older session, I set setExceptionIfMaximumExceeded(false). I knew there is an expireNow() in onAuthentication(), and the old session is removed from SessionRegistry (the sessionRegistry.getAllSessions() returns no more than 1 element). But the old session is still valid. I implemente a SessionInformationExpiredStrategy, and inject it by .sessionManagement(x->x.sessionConcurrency(c->c.expiredSessionStrategy(new GJSJExpiredSessionStrategy()))), but when I debug the code, it not enter my own Strategy.
So what should I do?
Thanks!
Comment From: sjohnr
Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. We prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add a minimal sample that reproduces this issue if you feel this is a genuine bug.