I got around to writing the filter and here it is. Almost everyone using a Hibernate backed UserDetails implementation and Acegi is going to have a similar problem so I figured I would post a pretty skeletal solution hacked together from the source of HttpContextIntegrationFilter.
The code:
Code:
public class ReassociateObjectWithSessionFilter implements InitializingBean, Filter {
private static final Logger logger = Logger.getLogger(ReassociateObjectWithSessionFilter.class);
private static final String FILTER_APPLIED = "__acegi_object_reassociation_filter_applied";
public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
private PersistenceManager persistenceManager;
private Class context = SecurityContextImpl.class;
public PersistenceManager getPersistenceManager() {
return persistenceManager;
}
public void setPersistenceManager(PersistenceManager persistenceManager) {
this.persistenceManager = persistenceManager;
}
public void afterPropertiesSet() throws Exception {
}
/**
* Does nothing. We use IoC container lifecycle services instead.
*/
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
}
else {
if (request != null) {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
HttpSession httpSession = null;
try {
httpSession = ((HttpServletRequest) request).getSession(false);
}
catch (IllegalStateException ignored) {}
if (httpSession != null) {
Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
if (contextFromSessionObject != null) {
if (contextFromSessionObject instanceof SecurityContext) {
//Reattach the user to a hibernate session and refresh its data
SecurityContext secContext = (SecurityContext)contextFromSessionObject;
User sessionUser = (User)secContext.getAuthentication().getPrincipal();
if (logger.isDebugEnabled()) {
logger.debug("Reattaching the object: " + sessionUser +
"to a hibernate session");
}
getPersistenceManager().refresh(sessionUser);
sessionUser = null;
}
else {
if (logger.isDebugEnabled()) {
logger.debug(
"HttpSession returned null object for ACEGI_SECURITY_CONTEXT - No security context could be found");
}
}
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(
"No HttpSession currently exists - hence I can't get the SecurityContext to find the user to reattach");
}
}
// Make the HttpSession null, as we want to ensure we don't keep
// a reference to the HttpSession laying around in case the
// chain.doFilter() invalidates it.
httpSession = null;
try {
chain.doFilter(request, response);
}
catch (IOException ioe) {
throw ioe;
}
catch (ServletException se) {
throw se;
}
finally {
}
}
}
public Class getContext() {
return context;
}
/**
* Does nothing. We use IoC container lifecycle services instead.
*/
public void init(FilterConfig filterConfig) throws ServletException {}
}
It is messy code that needs a little tidying, but it works. They key is the refresh() method from a Hibernate3 session. It reattaches the object to the session and refreshes its fields from the database.
I simply instantiate the filter in my app context and add it to the FilterChainProxy list.
Cheers,
Deakin.