In Our project We are using spring security 3.5 for authorisation purpose only.
We use j2ee container for authentication and donot rely on spring for authentication and logout purposes.
Today we faced a cache problem with regards to authentication details.
For the first time if user A logs in his authentication details i.e his associated roles(granted authorities) are correctlly populated.
But when we try to logout user A and make user B to login due to some cache problem user A authentication details i.e his associated roles(granted authorities) are getting displayed when user B login in :-(
It means for spring though user B is logged in it still assumes user A is logged in which means the logout functionality went for a toss and we are not properly implemented the logout functionality.
As said earlier we are not replying on spring for built-in logout functionality.
We have resolved the problem by mimcing the spring builtin logout functionality and changing code when required especially "filterProcessesUrl" variable in LogoutFilter class. Initially it will have value something like "j_securitycheck_logout" but we changed to our project specific logout URL.
Just pasting code for the benefit of other developers.
spring security.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Sample namespace-based configuration
-
-->
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<global-method-security pre-post-annotations="enabled">
</global-method-security>
<http auto-config="true" use-expressions="true" access-denied-page="/accessDenied/showAccessDenied.html">
</http>
<!--
The final servlet filter in the default Spring Security filter chain,
FilterSecurityInterceptor, is the filter responsible for coming up with a
decision on whether or not a particular request will be accepted or denied. At this
point the FilterSecurityInterceptor filter is invoked, the principal has already
been authenticated, so the system knows that they are valid users.
-->
<b:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map path-type="ant">
<filter-chain pattern="/**" filters="exceptionTranslationFilter,securityContextPersistenceFilter,j2eePreAuthFilter,fsi,logoutFilter"/>
</filter-chain-map>
</b:bean>
<b:bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<b:property name="forceEagerSessionCreation" value="true"/>
</b:bean>
<b:bean id="logoutFilter" class="demo.LogoutFilterWrapper">
</b:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref='preAuthenticatedAuthenticationProvider'/>
</authentication-manager>
<b:bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<b:property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</b:bean>
<b:bean id="preAuthenticatedUserDetailsService"
class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>
<b:bean id="j2eePreAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
<b:property name="authenticationManager" ref="authenticationManager"/>
<b:property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
<b:property name="continueFilterChainOnUnsuccessfulAuthentication" value="false"/>
</b:bean>
<b:bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
<b:property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
<b:property name="userRoles2GrantedAuthoritiesMapper" ref="j2eeUserRoles2GrantedAuthoritiesMapper"/>
</b:bean>
<b:bean id="j2eeMappableRolesRetriever" class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever">
<!-- <property name="webXmlInputStream"><bean factory-bean="webXmlResource" factory-method="getInputStream"/>
</property> -->
</b:bean>
<b:bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
<b:constructor-arg ref="servletContext"/>
<b:constructor-arg value="/WEB-INF/web.xml"/>
</b:bean>
<b:bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
<b:bean id="j2eeUserRoles2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
<b:property name="attributePrefix" value="dummy"/>
</b:bean>
<!-- AffirmativeBased== If any voter grants access, access is immediately granted,regardless of previous denials. -->
<!-- decisionVoters property => This property is auto-configured until we declare our own AccessDecisionManager. The default
AccessDecisionManager requires us to declare the list of voters who are consulted
to arrive at the authentication decisions. -->
<b:bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<b:property name="allowIfAllAbstainDecisions" value="false"/>
<b:property name="decisionVoters">
<b:list>
<b:ref bean="roleVoter"/>
<b:ref bean="webExpressionVoter"/>
</b:list>
</b:property>
</b:bean>
<!-- roleVoter ==>
Checks that the user has the
GrantedAuthority matching
the declared role. Expects the
access attribute to define
a comma-delimited list of
GrantedAuthority names. The
ROLE_ prefix is expected, but
optionally configurable.
webExpressionVoter=> SpEL handling is supplied by a different Voter
implementation, o.s.s.web.access.expression.WebExpressionVoter,
which understands how to evaluate the SpEL expressions.
The <filter-security-metadata-source> element is responsible for
configuring the SecurityMetadataSource implementation used by the
FilterSecurityInterceptor, including the URL declarations and roles
required to access them.
-->
<b:bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<b:bean id="webExpressionVoter" class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
<b:bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<b:property name="authenticationManager" ref="authenticationManager"/>
<b:property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<b:property name="securityMetadataSource">
<filter-invocation-definition-source use-expressions="true">
<!-- <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/css/**" filters="none"/>
<intercept-url pattern="/login.jsp*" filters="none"/>
<intercept-url pattern="/**" access="hasRole('sls')"/>
-->
</filter-invocation-definition-source>
</b:property>
</b:bean>
<!-- ExceptionTranslationFilter, one of
the last servlet filters in the standard Spring Security filter chain, is responsible for
examining exceptions thrown during the authentication and authorization processes
(in FilterSecurityInterceptor, the culmination of the filter chain), and reacting
appropriately to them. -->
<b:bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<b:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<b:property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</b:bean>
<b:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<b:property name="loginFormUrl" value="/login.jsp"/>
</b:bean>
<b:bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<b:property name="errorPage" value="/accessDenied/showAccessDenied.html"/>
</b:bean>
<b:bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>
</b:beans>
LogoutFilterWrapper.java
import java.io.IOException;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
public class LogoutFilterWrapper implements Filter {
public static final Logger LOGGER = Logger.getLogger(LogoutFilterWrapper.class);
private String logoutSuccessfulUrl="/Login/Login";
private String logoutSuccessfulInactivityUrl="/Login/Login";
private LogoutFilter filter;
protected void initialize() {
LOGGER.debug("LogoutFilterWrapper:Entered into initialize method");
final SecurityContextLogoutHandler context = new SecurityContextLogoutHandler();
context.setInvalidateHttpSession( true );
this.filter =new LogoutFilter( new CustomLogoutSuccessHandler(), new LogoutHandler[] { context } );
}
public void setLogoutSuccessfulUrl(String inUrl ) {
this.logoutSuccessfulUrl = inUrl;
}
public void setLogoutSuccessfulUrlInactivity(String inUrl ) {
this.logoutSuccessfulInactivityUrl = inUrl;
}
@Override
public final void init(FilterConfig inFilterConfig ) throws ServletException {
LOGGER.debug("LogoutFilterWrapper:Entered into init method");
initialize();
this.filter.init(inFilterConfig);
}
@Override
public void destroy() {
this.filter.destroy();
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
LOGGER.debug("LogoutFilterWrapper:Entered into doFilter method");
if(this.filter==null) {
initialize();
}
this.filter.doFilter( arg0, arg1, arg2 );
}
/**
* Success Handler
*/
private class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String targetUrl = "";
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession().removeAttribute(Globals.USER);
request.getSession().invalidate();
redirectStrategy.sendRedirect( request, response, targetUrl );
}
}
}
LogoutFilter.java
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
/**
* Logs a principal out.
* <p>
* Polls a series of {@link LogoutHandler}s. The handlers should be specified in the order they are required.
* Generally you will want to call logout handlers <code>TokenBasedRememberMeServices</code> and
* <code>SecurityContextLogoutHandler</code> (in that order).
* <p>
* After logout, a redirect will be performed to the URL determined by either the configured
* <tt>LogoutSuccessHandler</tt> or the <tt>logoutSuccessUrl</tt>, depending on which constructor was used.
*
* @author Ben Alex
*/
public class LogoutFilter extends GenericFilterBean {
public static final Logger LOGGER = Logger.getLogger(LogoutFilter.class);
//~ Instance fields ================================================================================================
private String filterProcessesUrl = "/LogOut.html";
private List<LogoutHandler> handlers;
private LogoutSuccessHandler logoutSuccessHandler;
//~ Constructors ===================================================================================================
/**
* Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the target destination
* after logging out. The list of <tt>LogoutHandler</tt>s are intended to perform the actual logout functionality
* (such as clearing the security context, invalidating the session, etc.).
*/
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,LogoutHandler... handlers) {
Assert.notEmpty(handlers, "LogoutHandlers are required");
this .handlers = Arrays.asList(handlers);
Assert.notNull(logoutSuccessHandler,
"logoutSuccessHandler cannot be null");
this .logoutSuccessHandler = logoutSuccessHandler;
}
public LogoutFilter(String logoutSuccessUrl,LogoutHandler... handlers) {
Assert.notEmpty(handlers, "LogoutHandlers are required");
this .handlers = Arrays.asList(handlers);
Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl)
|| UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if (StringUtils.hasText(logoutSuccessUrl)) {
urlLogoutSuccessHandler
.setDefaultTargetUrl(logoutSuccessUrl);
}
logoutSuccessHandler = urlLogoutSuccessHandler;
}
//~ Methods ========================================================================================================
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
LOGGER.debug("LogoutFilter doFilter entered");
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
LOGGER.debug("Logging out user '" + auth + "' and transferring to logout destination");
for (LogoutHandler handler : handlers) {
handler.logout(request, response, auth);
}
logoutSuccessHandler.onLogoutSuccess(request, response,auth);
return;
}
chain.doFilter(request, response);
}
/**
* Allow subclasses to modify when a logout should take place.
*
* @param request the request
* @param response the response
*
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
*/
protected boolean requiresLogout(HttpServletRequest request,
HttpServletResponse response) {
String uri = request.getRequestURI();
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl),
filterProcessesUrl + " isn't a valid value for"
+ " 'filterProcessesUrl'");
this .filterProcessesUrl = filterProcessesUrl;
}
protected String getFilterProcessesUrl() {
return filterProcessesUrl;
}
}
For logout we use common spring action calls
our jsp contain the call :
<a href="<c:url value='/LogOut.html'/>">Log Out</a>
@RequestMapping(value="/LogOut.html")
public ModelAndView logOut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession().removeAttribute(Globals.USER);
request.getSession().invalidate();
return new ModelAndView(Globals.login);
}
Wednesday, 6 July 2011
Spring Security3.5 Authorisation Cache Problem
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment