After implementing Spring Session Management Spring security keeps forwarding me to the login page - spring

I am working on JEE application. We recently switched from container based security to spring security. I am now trying move session handling out of the container and into the application using spring-session-jdbc. I've created the required tables in our database and created the following SessionConfig file:
#Configuration
#EnableJdbcHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
#Bean
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:jboss/MyDS");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public FindByIndexNameSessionRepository<?> sessionRepository(PlatformTransactionManager txManager,
DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
return new JdbcIndexedSessionRepository(jdbcTemplate, txTemplate);
}
}
We have a security config where I autowire the the sessionRepository and use it to create the SessionAuthenticationStrategy like:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
public FindByIndexNameSessionRepository<?> repo;
#Override
public void configure(WebSecurity web) throws Exception {
// put all static resource or external urls here
web.ignoring().antMatchers("/external/**", "/react/**", "/images/**", "/js/**", "/css/**",
"/vendor/**", "/fonts/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
String maxSessions =
GenericPropertiesReader.getInstance().getValue("config.logins.max.sessions");
http.sessionManagement()// set the session management
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.invalidSessionUrl("/login.html") // no user session forward here
.maximumSessions(Integer.valueOf(maxSessions))// 1 or -1 for unlimited
.maxSessionsPreventsLogin(false)// new session will terminate old session and forward them
// to the log in page
.expiredUrl("/login.html?type=expired-session");
http.headers().frameOptions().disable();
http.authorizeRequests()// put any antMatchers after this
.antMatchers("/login.html").permitAll() // permit any login page
.anyRequest().authenticated().and().formLogin() // we are using form login
.loginPage("/login.html") // show our custom login form
.loginProcessingUrl("/login") // post to Spring's action URL so our custom auth provider is invoked
.successHandler(successHandler()).failureHandler(failureHandler())
.permitAll() // so anyone can see it
.and().logout().logoutUrl("/logout")
.logoutSuccessHandler(new MyLogoutSuccessHandler())// our custom logout handler
.invalidateHttpSession(true) // delete session, need more work??
.deleteCookies("JSESSIONID") // and get rid of that cookie so they can't auto-login again
.permitAll()
.and().x509().x509AuthenticationFilter(this.x509AuthFilter());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(x509AuthProvider()).authenticationProvider(loginAuthProvider());
}
#Bean
public PreAuthenticatedAuthenticationProvider x509AuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(x509PreAuthUserDetailsService());
return provider;
}
#Bean // this irks me.
public AuthenticationManager myAuthenticationManager() throws Exception {
return this.authenticationManager();
}
#Bean
X509AuthenticationFilter x509AuthFilter() throws Exception {
X509AuthenticationFilter filter = new X509AuthenticationFilter();
filter.setAuthenticationSuccessHandler(x509SuccessHandler());
filter.setPrincipalExtractor(x509Extractor());
filter.setAuthenticationManager(this.myAuthenticationManager());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}
#Bean
public X509PrincipalExtractor x509Extractor() {
return new MyX509DodIdExtractor();
}
#Bean
public MyX509PreAuthUserDetailsService x509PreAuthUserDetailsService() {
return new MyX509PreAuthUserDetailsService();
}
#Bean
public MyAuthenticationProvider loginAuthProvider() {
return new MyAuthenticationProvider ();
}
#Bean
MyAuthenticationSuccessHandler x509SuccessHandler() {
MyAuthenticationSuccessHandler handler = new MyAuthenticationSuccessHandler ();
handler.setForwardResonse(false);
return handler;
}
#Bean
public MyAuthenticationSuccessHandler successHandler() {
return new MyAuthenticationSuccessHandler();
}
#Bean
public MyAuthenticationFailureHandler failureHandler() {
MyAuthenticationFailureHandler failureHandler = new MyAuthenticationFailureHandler();
failureHandler.setExceptionMappings(LoginFailures.exceptionMap());
failureHandler.setDefaultFailureUrl("/login.html?login-failure=" + LoginFailures.DEFAULT.code);
return failureHandler;
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public SpringSessionBackedSessionRegistry<? extends Session> sessionRegistry()
throws IllegalArgumentException, NamingException {
return new SpringSessionBackedSessionRegistry<>(repo);
}
#Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy()
throws IllegalArgumentException, NamingException {
ConcurrentSessionControlAuthenticationStrategy sessionAuthenticationStrategy =
new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
return sessionAuthenticationStrategy;
}
}
I see the session tables in the database being filled when attempting to login. I never hit any of the authentication code when debugging. I just am redirected to the login page every time.
I feel like I must be missing something obvious. I was getting errors that there was no unique bean of type FindByIndexNameSessionRepository<?> until I changed the name of the bean in SessionConfig to sessionRepository. Which makes me think there's another bean of that type being instantiated by Spring (not in our code base) that might be interfering?

Related

Error occured when Spring Security CustomLoginFilter is applied! somebody help me

I want to apply CustomLoginProcessingFilter in my application but i can't figure out how it works!
I'm using Spring boot 2.7.2, the lastest version when i started studying this.
here's my code
Another custom providers or custom detail services work so well.
But, once i enroll new bean fore login processing filter, AjaxLoginProcessingFilter, they tell me that i need to specify authentitcationManager!
so, i added at filterChain method this in SecurityConfig.java, but it doesn't work.
enter image description here
/**
----------------- SecurityConfig -------------------------
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
private final CustomAuthenticationSuccessHandler authenticationSuccessHandler;
private final CustomAuthenticationFailureHandler authenticationFailureHandler;
private final FormAuthenticationDetailsSource authenticationDetailsSource;
private final AjaxLoginProcessingFilter ajaxLoginProcessingFilter;
#Bean
AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
return builder.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(registry -> {
registry.antMatchers("/","/users","user/login/**","/login*").permitAll()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("/config").hasRole("ADMIN")
.anyRequest().authenticated();
}).formLogin(login -> {
login.loginPage("/login")
.loginProcessingUrl("/login_proc")
.defaultSuccessUrl("/")
.authenticationDetailsSource(authenticationDetailsSource)
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.permitAll();
}).exceptionHandling(exception -> {
exception.accessDeniedHandler(accessDeniedHandler());
})
.addFilterBefore(ajaxLoginProcessingFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
CustomAccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler();
accessDeniedHandler.setErrorPage("/denied");
return accessDeniedHandler;
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() throws Exception {
return (web) -> web.ignoring().antMatchers("/resources/**");
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/*Explanation:
In the old version you inject AuthenticationManagerBuilder, set userDetailsService, passwordEncoder and build it.
But authenticationManager is already created in this step.
It is created the way we wanted (with userDetailsService and the passwordEncoder).
https://stackoverflow.com/questions/72381114/spring-security-upgrading-the-deprecated-websecurityconfigureradapter-in-spring
*/
#Bean
CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Bean
public AuthenticationProvider authenticationProvider() {
return new CustomAuthenticationProvider();
}
}
-++------------------ AjaxLoginProcessingFilter ---------------------
#Component("loginProcessingFilter")
public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private ObjectMapper objectMapper = new ObjectMapper();
public AjaxLoginProcessingFilter() {
super(new AntPathRequestMatcher("/api/login"));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if(isAjax(request)){
throw new IllegalStateException("Authentication is not supported");
}
AccountDto accountDto = objectMapper.readValue(request.getReader(), AccountDto.class);
if(StringUtils.isEmpty(accountDto.getUsername()) || StringUtils.isEmpty(accountDto.getPassword())){
throw new IllegalArgumentException("Username or password is not empty");
}
AjaxAuthenticationToken authenticationToken = new AjaxAuthenticationToken(accountDto.getUsername(), accountDto.getPassword());
return getAuthenticationManager().authenticate(authenticationToken);
}
private boolean isAjax(HttpServletRequest request) throws IOException {
if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
return true;
}
return false;
}
}

spring boot API + security +ldap

I want to achieve LDAP verification without form login, But not sure how to achieve it.
EDITED: I have a custom POST API /login which accept userId and password and validate it and creates a session cookies/Token.
Few doubts:
how to point to custom login URL endpoint?
How internally the form login used to validate the cookie once we successfully validated the credentials? How to achieve the same with custom login endpoint?
Do i have to change something in CRSF?
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LdapProperties ldapProperties;
#Bean
public LdapAuthenticationProvider
authenticationProvider(final LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
}
#Bean
public BindAuthenticator authenticator(final BaseLdapPathContextSource contextSource) {
final BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] {
ldapProperties.getUserDnPatterns() });
return authenticator;
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter(ldapProperties.getSearchFilter())
.contextSource(contextSource());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.formLogin().and()
.authorizeRequests()
.anyRequest().fullyAuthenticated().and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login");
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.debug(true);
}
#Bean
public LdapContextSource contextSource() {
final LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(ldapProperties.getUrl());
contextSource.setBase(ldapProperties.getBase());
contextSource.setUserDn(ldapProperties.getUserDn());
contextSource.setPassword(ldapProperties.getUserDnPaswrd());
contextSource.setPooled(true);
contextSource.afterPropertiesSet();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate() {
final LdapTemplate ldapTemplate = new LdapTemplate(
contextSource());
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
}

Spring Boot Resource Server Invalid Access Token

My configuration for the Spring Boot resource server is provided:
#Configuration
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "couponservice";
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/couponapi/coupons/{code:^[A-Z]*$}").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST, "/couponapi/coupons").hasRole("ADMIN")
.anyRequest().denyAll().and().csrf().disable();
}
// #Bean
// public TokenStore tokenStore() {
// return new JwtTokenStore(jwtAccessTokenConverter());
// }
//
// #Bean
// public JwtAccessTokenConverter jwtAccessTokenConverter() {
//
// JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// jwtAccessTokenConverter.setVerifierKey(publicKey);
//
// return jwtAccessTokenConverter;
// }
}
The application.properties file is provided:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=testuser
spring.datasource.password=testpassword
server.port=9091
spring.thymeleaf.cache=false
spring.main.allow-bean-definition-overriding=true
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:9092/oauth/token_key
# security.oauth2.resource.jwt.key-uri=http://localhost:9092/oauth/token_key
If I keep the JwtAccessTokenConverter and use the correct public key, the code is working. But, I would like to connect using the auth URL provided in the properties files.
Now, when I make the GET request, I see the invalid access toekn. Whats the issue here and how do I resolve it?
Looks like you need to tell Spring WHAT MECHANISM to use to authorize. Maybe compare to this Curity example, which should provide enough clues to overcome your problem:
Code
Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
String issuerUri;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/services").hasAuthority("SCOPE_services:read")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(jwt ->
jwt.decoder(JwtDecoders.fromIssuerLocation(issuerUri))
)
);
}
}

Spring Security 3.2: #Autowire doesn't work with java configuration and custom AuthenticationProvider in Spring MVC application?

This problem is relatively well discussed in several blog posts and SO questions. Nevertheless, I wasn't able to find one specifically addressing the problem with java configuration. I'm suspecting that I'm doing something wrong in my java configuration files, since I've found some posts indicating that the problem can be resolved by removing the debug XML tag (https://jira.springsource.org/browse/SEC-1885).
I'm using 3.2.0.RELEASE of spring security, and 3.2.6.RELEASE of spring framework. Below the main files used in the spring security/mvc configuration and the custom AuthenticationProvider.
WebConfig:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.mypackage"})
#ImportResource( { "classpath:/spring-data.xml", "classpath:/trace-context.xml" })
#EnableTransactionManagement
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
#Bean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
#Bean(destroyMethod = "shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabase("target/temp.db");
}
#Bean
public RepositoryInitializer repositoryInitializer() {
return new RepositoryInitializer();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
#Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
cookieLocaleResolver.setDefaultLocale(StringUtils.parseLocaleString("en"));
return cookieLocaleResolver;
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages/messages", "classpath:messages/validation");
// if true, the key of the message will be displayed if the key is not
// found, instead of throwing a NoSuchMessageException
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
// # -1 : never reload, 0 always reload
messageSource.setCacheSeconds(0);
return messageSource;
}
}
WebInitializer:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[] { characterEncodingFilter, new SiteMeshFilter()};
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
//servletContext.addListener(new HttpSessionEventPublisher());
}
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().permitAll();
// .antMatchers("/", "/login").permitAll()
// .anyRequest().authenticated();
http
.formLogin()
.defaultSuccessUrl("/hello")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.permitAll();
http
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(new ApplicationAuthenticationProvider());
}
}
WebSecurityInitializer:
public class WebSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
AuthenticationProvider:
#Component(value = "authenticationProvider")
public class ApplicationAuthenticationProvider implements AuthenticationProvider {
#Autowired
public UserService userService;
public ApplicationAuthenticationProvider() {}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.loadUserByUsername(username);
if (user == null) {
throw new BadCredentialsException("Username not found.");
}
if (!password.equals(user.getPassword())) {
throw new BadCredentialsException("Wrong password.");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(username, password, authorities);
}
#Override
public boolean supports(Class<?> arg0) {
return true;
}
}
UserService:
#Service
public class UserService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public User loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
Spring is throwing an exception while it is building its application context (during application initialization):
[ERROR] [main 11:53:37] (FrameworkServlet.java:initServletBean:467) Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationProvider': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: public com.evidencefactory.service.UserService com.evidencefactory.security.ApplicationAuthenticationProvider.userService; nested exception is java.lang.IllegalArgumentException: Can not set com.evidencefactory.service.UserService field com.evidencefactory.security.ApplicationAuthenticationProvider.userService to sun.proxy.$Proxy71
I don't understand why it is happening, but if I remove the UserDetailsService interface implementation from UserService class, then the application starts successfully. However, when ApplicationAuthenticationProvider is invoked by Spring, the UserService is not autowired into it and the application throws a NullPointerException.
java.lang.NullPointerException
at com.evidencefactory.security.ApplicationAuthenticationProvider.authenticate(ApplicationAuthenticationProvider.java:33)
Figured out how to put it to work, although there still some issues unanswered.
1) I still don't know why Spring context initialization fails when UserService implements UserDetailsService. Given that I'm not seeing use for it, since I'm using a custom AuthenticationProvider, I just removed this implementation and things are ok for now.
To the best of my knowledge (from what I could understand from my first initial reading of Spring Security reference documentation) providing a custom AuthenticationProvider or an UserDetailsService implementation are exclusive alternatives.
2) As noticed by one of the respondents (#Sotirios Delimanolis) I was instantiating ApplicatinoAuthenticationProvider by hand and since it wasn't being managed by Spring this instance would not have an UserService instance autowired into it. Based on this, I changed WebSecurityConfig to get an autowired instance of ApplicationAuthenticationProvider as can be seen below:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(authenticationProvider);
}
}
This wasn't still sufficient, because ApplicationAuthenticationProvider wasn't being autowired into WebSecurityConfig. Based on this link Spring Security 3.1.3 #Autowired not Work when using WebApplicationInitializer I noticed that this was because security config should have a component scan declaration too. Adding #ComponentScan(basePackages = {"com.mypackage"}) to WebSecurityConfig resolved the problem.
I'm going to assume that UserService is a class and has some #Transactional annotation either on itself or one of its methods.
You'll need to add CGLIB to your classpath and change your #EnableTransactionManagement to
#EnableTransactionManagement(proxyTargetClass = true)
so that Spring uses CGLIB proxying (which can proxy classes) instead of JKD proxies (which cannot).
Alternatively, you can create an interface UserService and implement (and annotate with #Service) a UserServiceImpl class. Your autowired UserService field would remain the same, but Spring will be able to use JDK proxies.

How can I have list of all users logged in (via spring security) my web application

I'm using spring security in my web application, and now I want to have a list of all users who are logged in my program.
How can I have access to that list? Aren't they already kept somewhere within spring framework? Like SecurityContextHolder or SecurityContextRepository?
For accessing the list of all logged in users you need to inject SessionRegistry instance to your bean.
#Autowired
#Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
And then using injcted SessionRegistry you can access the list of all principals:
List<Object> principals = sessionRegistry.getAllPrincipals();
List<String> usersNamesList = new ArrayList<String>();
for (Object principal: principals) {
if (principal instanceof User) {
usersNamesList.add(((User) principal).getUsername());
}
}
But before injecting session registry you need to define session management part in your spring-security.xml (look at Session Management section in Spring Security reference documentation) and in concurrency-control section you should set alias for session registry object (session-registry-alias) by which you will inject it.
<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
<security:session-management session-fixation-protection="migrateSession" session-authentication-error-url="/login.jsp?authFailed=true">
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.html" session-registry-alias="sessionRegistry"/>
</security:session-management>
...
</security:http>
In JavaConfig, it would look like this:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
}
With the calling code looking like this:
public class UserController {
#Autowired
private SessionRegistry sessionRegistry;
public void listLoggedInUsers() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
for(final Object principal : allPrincipals) {
if(principal instanceof SecurityUser) {
final SecurityUser user = (SecurityUser) principal;
// Do something with user
System.out.println(user);
}
}
}
}
Note that SecurityUser is my own class which implements UserDetails.
Please correct me if I'm wrong.
I think #Adam's answer is incomplete. I noticed that sessions already expired in the list were appearing again.
public class UserController {
#Autowired
private SessionRegistry sessionRegistry;
public void listLoggedInUsers() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
for (final Object principal : allPrincipals) {
if (principal instanceof SecurityUser) {
final SecurityUser user = (SecurityUser) principal;
List<SessionInformation> activeUserSessions =
sessionRegistry.getAllSessions(principal,
/* includeExpiredSessions */ false); // Should not return null;
if (!activeUserSessions.isEmpty()) {
// Do something with user
System.out.println(user);
}
}
}
}
}
Hope it helps.
Please correct me if I'm wrong too.
I think #Adam's and #elysch`s answer is incomplete. I noticed that there are needed to add listener:
servletContext.addListener(HttpSessionEventPublisher.class);
to
public class AppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
...
servletContext.addListener(HttpSessionEventPublisher.class);
}
with security conf:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
And then you will get current online users!
You need to inject SessionRegistry (as mentioned eariler) and then you can do it in one pipeline like this:
public List<UserDetails> findAllLoggedInUsers() {
return sessionRegistry.getAllPrincipals()
.stream()
.filter(principal -> principal instanceof UserDetails)
.map(UserDetails.class::cast)
.collect(Collectors.toList());
}
Found this note to be quite important and relevant:
"[21] 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. Session-management functionality has to be
handled separately in these cases."
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/session-mgmt.html#d0e4399
Also, apparently a lot of people have troubles getting sessionRegistry.getAllPrincipals() returning something different from an empty array. In my case, I fixed it by adding the sessionAuthenticationStrategy to my custom authenticationFilter:
#Bean
public CustomUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
...
authenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
//cf. https://stackoverflow.com/questions/32463022/sessionregistry-is-empty-when-i-use-concurrentsessioncontrolauthenticationstrate
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
List<SessionAuthenticationStrategy> stratList = new ArrayList<>();
SessionFixationProtectionStrategy concStrat = new SessionFixationProtectionStrategy();
stratList.add(concStrat);
RegisterSessionAuthenticationStrategy regStrat = new RegisterSessionAuthenticationStrategy(sessionRegistry());
stratList.add(regStrat);
CompositeSessionAuthenticationStrategy compStrat = new CompositeSessionAuthenticationStrategy(stratList);
return compStrat;
}
Similar to #rolyanos solution, mine for me always works:
- for the controller
#RequestMapping(value = "/admin")
public String admin(Map<String, Object> model) {
if(sessionRegistry.getAllPrincipals().size() != 0) {
logger.info("ACTIVE USER: " + sessionRegistry.getAllPrincipals().size());
model.put("activeuser", sessionRegistry.getAllPrincipals().size());
}
else
logger.warn("EMPTY" );
logger.debug(log_msg_a + " access ADMIN page. Access granted." + ANSI_RESET);
return "admin";
}
- for the front end
<tr th:each="activeuser, iterStat: ${activeuser}">
<th><b>Active users: </b></th> <td align="center" th:text="${activeuser}"></td>
</tr>
- for spring confing
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutSuccessUrl("/home")
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
http.authorizeRequests()
.antMatchers("/", "/home")
.permitAll()
.antMatchers("/admin")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/home")
.defaultSuccessUrl("/main")
.permitAll()
.and()
.logout()
.permitAll();
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
http.authorizeRequests().antMatchers("/webjars/**").permitAll();
http.exceptionHandling().accessDeniedPage("/403");
}

Resources