Hi all,
I am moving my application from Spring Security 2.x to 3.x and I am having some issues.
What I had earlier is that Spring Security was preventing that same user (same credentials) can log to my application from two different PC's (or same machine but two different browsers).
For this I was using ConcurrentSessionControl.
Now with Spring Security 3.0 I am almost done with moving existing features to new configuration but I have still this problem.
First of all there is web-security-servlet.xml
If user is successfully authenticated I want to insert that information in DB (for some later UserActions logs), but anyway here is the code:Code:<http auto-config="true"> <intercept-url pattern="/login.htm" filters="none" /> <intercept-url pattern="/main_dashboard.htm" access="ROLE_SYSTEM_ADMIN,ROLE_STATISTICS_USER,ROLE_ZABBIX_USER,ROLE_PERFORMANCE_USER,ROLE_USER_MANAGEMENT_USER,ROLE_APPLICATION_MANAGEMENT_USER"/> <intercept-url pattern="/statistics/**" access="ROLE_SYSTEM_ADMIN,ROLE_STATISTICS_USER"/> <intercept-url pattern="/performance/system_monitoring.htm" access="ROLE_SYSTEM_ADMIN,ROLE_ZABBIX_USER"/> <intercept-url pattern="/performance/**" access="ROLE_SYSTEM_ADMIN,ROLE_PERFORMANCE_USER"/> <intercept-url pattern="/provisioning/user**" access="ROLE_SYSTEM_ADMIN,ROLE_USER_MANAGEMENT_USER"/> <intercept-url pattern="/provisioning/**" access="ROLE_SYSTEM_ADMIN,ROLE_APPLICATION_MANAGEMENT_USER"/> <form-login login-page="/login.htm" authentication-success-handler-ref="customAuthenticationSuccessHandler" authentication-failure-handler-ref="exceptionMappingAuthenticationFailureHandler" /> <session-management invalid-session-url="/error_session_timeout.htm"> <concurrency-control error-if-maximum-exceeded="true" max-sessions="1" session-registry-ref="sessionRegistry" /> </session-management> </http> <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" /> <beans:bean id="customAuthenticationSuccessHandler" class="com.one2many.cmspgw.gui.web.handler.CustomAuthenticationSuccessHandler" p:defaultTargetUrl="/main_dashboard.htm"/> <beans:bean id="exceptionMappingAuthenticationFailureHandler" class="com.one2many.cmspgw.gui.web.handler.CustomExceptionMappingAuthenticationFailureHandler"> <beans:property name="exceptionMappings"> <beans:props> <beans:prop key="org.springframework.security.web.authentication.session.SessionAuthenticationException">/error_concurent_session.htm</beans:prop> <beans:prop key="org.springframework.security.authentication.BadCredentialsException">/login.htm?login_error=1</beans:prop> <beans:prop key="org.springframework.security.authentication.CredentialsExpiredException">/login.htm?login_error=2</beans:prop> <beans:prop key="org.springframework.security.authentication.LockedException">/login.htm?login_error=3</beans:prop> <beans:prop key="org.springframework.security.authentication.DisabledException">/login.htm?login_error=4</beans:prop> <beans:prop key="org.springframework.security.core.AuthenticationException">/login.htm?login_error=100</beans:prop> </beans:props> </beans:property> <beans:property name="defaultFailureUrl" value="/login.htm?login_error=1"></beans:property> </beans:bean> <beans:bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" p:invalidateHttpSession="true" /> <authentication-manager alias="authenticationManager"> <authentication-provider> <user-service id="userDetailService"> <user name="sysadmin" password="sysadmin" authorities="ROLE_SYSTEM_ADMIN"/> <user name="zabbix" password="zabbix" authorities="ROLE_ZABBIX_USER" /> <user name="performance_gui" password="performance_gui" authorities="ROLE_PERFORMANCE_USER" /> <user name="statistics_gui" password="statistics_gui" authorities="ROLE_STATISTICS_USER" /> <user name="user_management" password="user_management" authorities="ROLE_USER_MANAGEMENT_USER" /> <user name="app_management" password="app_management" authorities="ROLE_APPLICATION_MANAGEMENT_USER" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
In case that there is some exception during authentication then I have CustomExceptionMappingAuthenticationHandler. Why it is custom? Well I need info (ip address) where user was last successfully logged in, so I am tempering redirect URL (adding more params):Code:... ommited here for clarity ... public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { ... omitted for clarity ... @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = ((UserDetails) authentication.getPrincipal()) .getUsername(); String loggedInIP = request.getRemoteAddr(); log .info( "+++ User with username: {} authenticated. IPAddress logging from: {}.", username, loggedInIP); userActionsBO.save(username, UserActionType.LOGIN, "User logged in from IP Address: [" + loggedInIP + "]", loggedInIP); super.onAuthenticationSuccess(request, response, authentication); } }
And now last but more interesting part I want to have custom LogoutController, so when I call "/logout.htm?username=[some_username]", I am using logoutHandler in order to invalidate previous session and to re-authenticate user with this new session.Code:... ommited here for clarity ... public class CustomExceptionMappingAuthenticationFailureHandler extends ExceptionMappingAuthenticationFailureHandler { ... omitted for clarity ... /** * Must override in order to get last login IPAddress */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String username = request.getParameter("j_username"); log.debug("+++ Authentication failure occurs for username: {}", username); String url = failureUrlMap.get(exception.getClass().getName()); String lastLoginIPAddress = userActionsBO .getLastLoggedInIPAddress(username); url = url + "?lastLoginIPAddress=" + lastLoginIPAddress + "&username=" + username; if (url != null) { getRedirectStrategy().sendRedirect(request, response, url); } else { super.onAuthenticationFailure(request, response, exception); } } ... setting failure exceptions map, ommited for clarity ... }
Here is the code:
Code:... ommited for clarity ... @Controller @RequestMapping("/logout.htm") public class LogoutController implements ApplicationContextAware { ... omitted for clarity ... @Autowired private AuthenticationManager authenticationManager; @RequestMapping(method = RequestMethod.GET, params = { "username" }) public ModelAndView invalidateOtherSession(HttpServletRequest request, HttpServletResponse response, @RequestParam(required = true) String username) { List<SessionInformation> sessionInformations = sessionRegistry.getAllSessions(username, true); for (SessionInformation sessionInformation : sessionInformations) { log.debug("+++++ Session Information: " + sessionInformation.getSessionId()); if (request.getSession().getId().equals(sessionInformation.getSessionId())) { log.debug("+++ Don't invalidate current session! +++"); } else { log.debug("+++ Invalidate other session and continue to work in current session +++"); sessionInformation.expireNow(); // Need to reauthenticate // FIXME: a.stoisavljevic - implement userBO.findByUsername(String username) return user // password should be switched to use from User object as well String password = request.getParameter("j_password"); Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password); Authentication authResult = ((ProviderManager)authenticationManager).getProviders().get(0).authenticate(authRequest); SecurityContextHolder.getContext().setAuthentication(authResult); if ( authResult.isAuthenticated() ) { log.debug("+++ User is reauthenticated! +++"); appContext.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } else { log.debug("+++ Can't publish event! +++"); } } } /* for (int i = 0; sessionInformations != null && i < sessionInformations.length; i++) { log.debug("+++++ Session Information: " + sessionInformations[i].getSessionId()); if (request.getSession().getId().equals(sessionInformations[i].getSessionId())) { log.debug("+++++ Doesn't invalidate current session! +++++"); } else { log.debug("+++++ Invalidate other session and continue to work! +++++"); sessionInformations[i].expireNow(); // Reauthenticate User user = userBO.findByUsername(username); Authentication authRequest = new UsernamePasswordAuthenticationToken(username, user.getPassword()); Authentication authResult = authenticationProvider.authenticate(authRequest); SecurityContextHolder.getContext().setAuthentication(authResult); if (authResult.isAuthenticated()) { log.debug("+++++ User is reauthenticated! +++++"); appContext.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } else { log.debug("+++++ Can't publish event! +++++"); } } } **/ return new ModelAndView(MAIN_DASHBOARD_REDIRECT_URL); } }



