Control Multiple session for Same User - spring

Trying to implement concurrent Session Control to invalidate the prior logged-in session and logout that session and let user login on another browser, so that a single user concurrently do not have multiple logins. I have used the following HTTP configurations in Web security configurations. But it's not working.
UaaWebSecurityconfiguration.java
#Autowired
public SessionRegistry sessionRegistry;
#Bean
public SessionRegistry sessionRegistry() {
if (sessionRegistry == null) {
sessionRegistry = new SessionRegistryImpl();
}
return sessionRegistry;
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf()
.disable()
.addFilterBefore(corsFilter, CsrfFilter.class).exceptionHandling()
.authenticationEntryPoint(problemSupport).accessDeniedHandler(problemSupport)
.and()
.rememberMe()
.key(jHipsterProperties.getSecurity().getRememberMe()
.getKey()).and().headers()
.frameOptions().disable()
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry)
.and()
.sessionFixation()
.changeSessionId()
.sessionAuthenticationStrategy(compositeSessionAuthenticationStrategy())
.and()
.authorizeRequests().antMatchers("/api/register")
.permitAll().antMatchers("/api/activate").permitAll().antMatchers("/api/authenticate")
.permitAll().antMatchers("/api/account/reset-password/init").permitAll()
.antMatchers("/api/account/reset-password/finish").permitAll()
.antMatchers("/api/profile-info").permitAll().antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll().antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
#Bean
public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
strategy.setMaximumSessions(1);
return strategy;
}
#Bean
public SessionFixationProtectionStrategy sessionFixationProtectionStrategy(){
return new SessionFixationProtectionStrategy();
}
#Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy(){
RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry());
return registerSessionAuthenticationStrategy;
}
#Bean
public CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy(){
List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
sessionAuthenticationStrategies.add(concurrentSessionControlAuthenticationStrategy());
sessionAuthenticationStrategies.add(sessionFixationProtectionStrategy());
sessionAuthenticationStrategies.add(registerSessionAuthenticationStrategy());
CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy = new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies);
return compositeSessionAuthenticationStrategy;
}

With this configuration, a session will be created in only one browser,
and all attempts to login to the new browser will not be successful as long as the session exists.
With such a minimal configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry());
}
the session will be created every time, and the old session will expired

Related

Request method 'POST' is not supported

I'm trying to upgrade Spring Boot from 2.7.6 to 3.0.1. I have a problem during the login action. The following is my new WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
private final CustomUserDetailsService customUserDetailsService;
private final CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
public WebSecurityConfig(CustomUserDetailsService customUserDetailsService, CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler) {
this.customUserDetailsService = customUserDetailsService;
this.customizeAuthenticationSuccessHandler = customizeAuthenticationSuccessHandler;
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/").permitAll()
.requestMatchers("/login").permitAll()
.authenticated()
.and()
.csrf().disable()
.formLogin()
.successHandler(customizeAuthenticationSuccessHandler)
.loginPage("/login")
.failureUrl("/login?error=true")
.usernameParameter("email")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)
.logoutSuccessUrl("/login?logout=true")
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.authenticationProvider(authenticationProvider());
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired=true");
return http.build();
}
// This second filter chain will secure the static resources without reading the SecurityContext from the session.
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
}
Follow my CustomUserDetailService:
#Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUserByEmail(String email) {
System.out.println(email);
User user = userRepository.findByEmail(email.toLowerCase());
System.out.println(user.getEmail());
return userRepository.findByEmail(email.toLowerCase());
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email.toLowerCase());
if (user != null) {
List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));;
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
}
When I run the application I see the login page, but when I enter the credential and press submit I receive the error:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported]
and Tomcat shows:
HTTP Status 405 – Method Not Allowed Type Status Report
Message Method 'POST' is not supported.
I searched for a solution but really I don't understand where is the problem.
To use multiple HttpSecurity instances, you must specify a security matcher, otherwise the first SecurityFilterChain will process all requests, and no requests will reach the second chain.
See this section of the Spring Security reference documentation.
In your case the SecurityFilterChain called resources is matching all requests, because you don't have a security matcher.
Since the resources chain does not configure formLogin then Spring Security does not create the default /login POST endpoint.
You can fix this by changing requests to:
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**") // the requests that this SecurityFilterChain will process
)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
If you want more details on the difference between authorizeHttpRequests and requestMatchers you can check out this question.
This error typically occurs when the method in the controller is not mapped to a post request. Should be something like:
#RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView login(...

Spring Security, remote user becomes null after session timeout

Here is my Spring Security config
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private String sLogoutUrl_;
private String sLoginPage_;
private String sLoginSuccessUrl_;
public WebSecurityConfig() {
sLogoutUrl_ = LnProperty.getSecurity(LnProperty.LOGOUTURL);
sLoginPage_ = LnProperty.getSecurity(LnProperty.LOGINPAGE);
sLoginSuccessUrl_ = LnProperty.getSecurity(LnProperty.LOGINSUCCESSURL);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> {
TomcatUser tomcatUser = LnProperty.getUser(username);
if (tomcatUser == null) {
throw new UsernameNotFoundException(username);
}
return new User(username, passwordEncoder().encode(tomcatUser.getPassword()), tomcatUser.getRoles());
});
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(sLoginPage_)
.loginProcessingUrl(sLoginPage_)
.defaultSuccessUrl(sLoginSuccessUrl_, true)
.failureHandler(authenticationFailureHandler(sLoginPage_))
.and()
.logout()
.logoutUrl(sLogoutUrl_)
.logoutSuccessHandler(logoutSuccessHandler(sLoginPage_))
.deleteCookies("JSESSIONID");
}
#Bean
public AuthenticationFailureHandler authenticationFailureHandler(String failureUrl) {
return new CustomAuthenticationFailureHandler(failureUrl);
}
#Bean
public LogoutSuccessHandler logoutSuccessHandler(String successUrl) {
return new CustomLogoutSuccessHandler(successUrl);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I tried adding this to htpp but didn't work.
.and()
.rememberMe()
.alwaysRemember(true);
Is there a way to stay logged in even after session timeout? It's fine if session attributes are cleared but I want the remote user not to be nulled after session timeout. Only logout the user if the logout url is entered, the browser is closed, or cookies/caches are deleted.

concurrent session management not working while setting sessionManagement().maximumSessions(1) allowing 2 sessions per user

I want a user to login only once and i am using the below mentioned code . It is working but allowing same user to login twice(creating 2 different sessions) and then in 3rd login attempt it is giving error message.##
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic()
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry()) ;
}
private SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("u")
.password("{noop}p") // Spring Security 5 requires specifying the password storage format
.roles("USER");
}

Spring Security Concurrent Session Control

I am trying to restrict user session to one at a time from anywhere. But it doesn't work. When I try to access the application with the same user on two navigator, I have access.
I noticed that when a user connects to the application on two different machines to start printing two different reports, there is an print that comes out instead of the other.
Thanks for help.
My Security config class :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/*#Autowired
private DataSource dataSource;*/
private AccessDeniedHandler accessDeniedHandler;
private AuthenticationSuccessHandler authenticationSuccessHandler;
private AuthenticationFailureHandler authenticationFailureHandler;
private UserDetailsService userDetailsService;
#Autowired
public SecurityConfiguration(
#Qualifier("customAccessDeneiedHandler")AccessDeniedHandler accessDeniedHandler,
#Qualifier("customSuccessHandler")AuthenticationSuccessHandler authenticationSuccessHandler,
#Qualifier("customAuthenticationFailureHandler")AuthenticationFailureHandler authenticationFailureHandler,
#Qualifier("customUserDetailsService")UserDetailsService userDetailsService) {
this.accessDeniedHandler = accessDeniedHandler;
this.authenticationSuccessHandler = authenticationSuccessHandler;
this.authenticationFailureHandler = authenticationFailureHandler;
this.userDetailsService = userDetailsService;
}
/* (non-Javadoc)
* #see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder)
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
//super.configure(auth);
auth.userDetailsService(userDetailsService) //auth.userDetailsService(utilisateurDetailsService)
.passwordEncoder(passwordEncoder());
}
//Authorization
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
//.antMatchers("/").permitAll()
.antMatchers("/ajouterassure", "/ajouterattributaire", "/ajouterbeneficiaire", "/ajouterpiecejustificative",
"/creerbordereauemission", "/creerbehorscoordination", "/creerbordereaupaie", "/ajouteravance",
"/creerbeavanceannuelle")
.hasAnyRole("DGA", "DGAA", "DR", "DRA", "CC", "CCA", "CI", "AS", "GUICHET", "CE", "CAP", "ADMIN") //.hasRole("ADMIN")
.antMatchers("/ajoutercentre", "/ajouteretablissementpaie", "/ajoutertypepj", "/ajoutertypedette",
"/ajoutersexe", "/ajoutersituationbeneficiaire", "/ajoutercategoriebeneficiaire",
"/ajoutercategorieattributaire", "/ajouterrevalorisation").hasAnyRole("DGA", "ADMIN") //hasAnyRole("CAP", "ADMIN")
.antMatchers("/payerdecompte").hasAnyRole("CAISSIER", "ADMIN")
.antMatchers("/ajouterutilisateur").hasAnyRole("CI", "ADMIN")
.anyRequest().authenticated()
.and()
//.httpBasic()
.formLogin()
.loginPage("/login")
//.loginProcessingUrl("/login")
.usernameParameter("identifiant")
.passwordParameter("mot_de_passe")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
//.defaultSuccessUrl("/")
.permitAll()
.and()
.logout().permitAll()
.and()
.sessionManagement() //Session controle concurence access
.maximumSessions(1)
.expiredUrl("/login?expired")
.sessionRegistry(sessionRegistry);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
//Session controle concurence access
//http.sessionManagement().maximumSessions(1);
}
/* (non-Javadoc)
* #see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.web.builders.WebSecurity)
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/resources/templates/errors/**", "/static/**", "/css/**", "/images/**", "/var/signatures/**");
//web.ignoring().antMatchers("/static/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean(name = "sessionRegistry")
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Autowired
#Lazy
private SessionRegistry sessionRegistry;
}
[Just in case someone finds it useful.]
Always add hashcode and equals methods in custom UserDetails class along with the below config in the spring security configuration class for the concurrent sessions to work.
protected void configure(HttpSecurity http) throws Exception
{
http.sessionManagement().maximumSessions(1);
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher()
{
return new HttpSessionEventPublisher();
}
You need to just add .maxSessionsPreventsLogin(true) after maximumSessions(1) and it stop logging in from other places util session expires here. So your configure method should look like this :-
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
//.antMatchers("/").permitAll()
.antMatchers("/ajouterassure", "/ajouterattributaire", "/ajouterbeneficiaire", "/ajouterpiecejustificative",
"/creerbordereauemission", "/creerbehorscoordination", "/creerbordereaupaie", "/ajouteravance",
"/creerbeavanceannuelle")
.hasAnyRole("DGA", "DGAA", "DR", "DRA", "CC", "CCA", "CI", "AS", "GUICHET", "CE", "CAP", "ADMIN") //.hasRole("ADMIN")
.antMatchers("/ajoutercentre", "/ajouteretablissementpaie", "/ajoutertypepj", "/ajoutertypedette",
"/ajoutersexe", "/ajoutersituationbeneficiaire", "/ajoutercategoriebeneficiaire",
"/ajoutercategorieattributaire", "/ajouterrevalorisation").hasAnyRole("DGA", "ADMIN") //hasAnyRole("CAP", "ADMIN")
.antMatchers("/payerdecompte").hasAnyRole("CAISSIER", "ADMIN")
.antMatchers("/ajouterutilisateur").hasAnyRole("CI", "ADMIN")
.anyRequest().authenticated()
.and()
//.httpBasic()
.formLogin()
.loginPage("/login")
//.loginProcessingUrl("/login")
.usernameParameter("identifiant")
.passwordParameter("mot_de_passe")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
//.defaultSuccessUrl("/")
.permitAll()
.and()
.logout().permitAll()
.and()
.sessionManagement() //Session controle concurence access
.maximumSessions(1)
.expiredUrl("/login?expired")
.sessionRegistry(sessionRegistry);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
//Session controle concurence access
//http.sessionManagement().maximumSessions(1);
}

How to configure custom authentication filter in spring security - using java config

I'm trying to configure a custom filter for spring security based authentication. It's a simple override of the usernamepasswordfilter. My problem is I don't know how to configure it using java configuration. Every time I hit "/admin/login" - it's entering my filter and causing an exception rather than going to the login page - but the antmatchers should be allowing access to /admin/login.
If I disable my filter, it works fine. I've read a few of the related questions but none seem to lead me to an answer.
Can anyone advise how to fix my configuration below to support the custom filter?
/**
* the security configuration.
*/
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
UserNotifier userNotifier() {
UserNotifier us = new UserNotifier();
return us;
}
#Bean
AuthenticationProvider customAuthenticationProvider() {
SystemUserAuthenticationProvider impl = new SystemUserAuthenticationProvider();
/* other properties etc */
return impl ;
}
#Bean
SystemUserService systemUserService(){
SystemUserService systemUserService = new SystemUserService();
return systemUserService;
}
#Bean
SystemAuthenticationFilter systemAuthenticationFilter() throws Exception {
SystemAuthenticationFilter f = new SystemAuthenticationFilter();
f.setAuthenticationManager(this.authenticationManager());
f.setPasswordParameter("password");
f.setUsernameParameter("email");
f.setPostOnly(true);
f.setAuthenticationFailureHandler(exceptionMappingAuthenticationFailureHandler());
f.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
f.setFilterProcessesUrl("/login");
return f;
}
#Bean
SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler(){
SavedRequestAwareAuthenticationSuccessHandler sv = new SavedRequestAwareAuthenticationSuccessHandler();
sv.setDefaultTargetUrl("/admin/customers");
return sv;
}
#Bean
AuditorAware<SystemUser> auditorAware(){
SystemUserAuditorAware adw = new SystemUserAuditorAware();
return adw;
}
#Bean
ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler(){
ExceptionMappingAuthenticationFailureHandler ex = new ExceptionMappingAuthenticationFailureHandler();
Map<String, String> mappings = new HashMap<String, String>();
mappings.put("org.springframework.security.authentication.CredentialsExpiredException", "/admin/login?reset");
mappings.put("org.springframework.security.authentication.LockedException", "/admin/login?locked");
mappings.put("org.springframework.security.authentication.BadCredentialsException", "/admin/login?error");
mappings.put("org.springframework.security.core.userdetails.UsernameNotFoundException", "/admin/login?error");
ex.setExceptionMappings(mappings);
return ex;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**")
;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/login", "/admin/login/new**", "/admin/register", "/admin/logout", "/assets/**", "/admin/session/timeout").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll()
.and()
.formLogin()
.failureHandler(exceptionMappingAuthenticationFailureHandler())
.loginProcessingUrl("/login")
.loginPage("/admin/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/admin/orders")
.and()
.logout()
.logoutUrl("/logout")
.and()
.requiresChannel()
.antMatchers("/admin/**").requiresSecure()
.and()
.addFilterBefore(systemAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Never mind, I fixed it by the changing the regex on the login processing url. It seemed to be interfering with the previous antmatcher.
So by changing the login processing url in the form login and custom filter configurations to "log_in", the login page now remains accessible without authorisation.

Resources