Announcement Announcement Module
Collapse
No announcement yet.
How to update the Authentication object on each request Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to update the Authentication object on each request

    Hi

    Since our UserDetails implementation, called User, uses Hibernate as persistence mechanism and since the User object has relations to other parts of the system (account, etc.) we need often need to merge the user stored in the Authentication object with the current persistent instance in the Hibernate Session to avoid various Hibernate problems. How ever this only works if the newly merged object is reused for the next merge operation.
    We therefore need a way to update the UserDetails instance in the immuteable Authentication instance used.

    We thought about updating the Authentication instance ourselves on every request, using forinstance a Filter, but this means that we will have to hardcode against the Authentication implementation used (here UsernamePasswordAuthenticationToken).

    I was wondering if Acegi has built in support for fetching a fresh UserDetails instance, for instance using the AcegiLoginService instance, on every request or if there's a better way to always have the Authentication instance up-to-date with regards to the UserDetails object?

  • #2
    Hi Jeppe,

    I am looking for a solution to the same problem. I need to reattach my "User" object to a Hibernate session so that I can use it properly. The trick is, as you have pointed out, to do it ONCE only. If you do it it multiple sessions, Hibernate will get upset at you if your user object has any collections associated with it.

    To my mind, this means that a Servlet Filter is the way to go. I also looked at writing one from scratch, but I am now investigating overriding the doFilter() functionality of HttpSessionContextIntegrationFilter.

    This filter goes and gets the SecureContext from the HttpSession and sticks it into the SecureContextHolder. If we can tack on a pm.lock(user, lockmode) then we will get our User reattached to a hibernate session.

    In essence, I'm looking at calling hibernatePM.lock(authentication.getPrincipal()) somewhere in doFilter().

    Because we're working with the Authentication interface, there is no hardcoding against implementations of it. All we need is getPrincipal().

    Ben, can you see a better way to do this?

    So, that's the theory, now to find the time to implement it. Feel free to have a stab at it if you think this is a good idea and please post your code if you do .

    Cheers,
    Deakin

    Comment


    • #3
      A filter does sound like a good solution, however, as I understand Hibernates lock() method, you have to ensure that the User hasn't been changed in another session (please correct me if I'm wrong).
      This might happen in most systems for instance if the user has been added/removed a rolem has been inactivated, etc. from an Administration module.
      In that case (again correct me if I'm wrong) you would have to use merge() instead of lock, in which case we would have to modify the immutable Authentication instance and thereby have to hardcode against its implementation when we're creating a new instance with the merged User object.

      Comment


      • #4
        Originally posted by SKI_BUM
        In that case (again correct me if I'm wrong) you would have to use merge() instead of lock, in which case we would have to modify the immutable Authentication instance and thereby have to hardcode against its implementation when we're creating a new instance with the merged User object.
        The merge() method AFAIK doesn't do what it sounds like you think it does. It takes values of the detached object you give it and overwrites the values of the persistent object and returns the persistent object. Here's the Javadoc for the Session.merge() method.

        Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".
        For my money, I'd just get the username or uid and do a fresh get in a filter. That's the only way to guarentee there are no problems. If you use Hibernates caching effectively, this shouldn't cause too many extra hits to the database.

        Comment


        • #5
          Or don't set a UserCache against your AbstractUserDetailsAuthenticationProvider (and anywhere else you're using it), and set AbstractSecurityInterceptor.alwaysReauthenticate=t rue. Thus every request will cause your AbstractUserDetailsAuthenticationProvider to go back to your UserDetailsService, which is probably a custom implementation that fronts Hibernate.

          Comment


          • #6
            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.

            Comment


            • #7
              I haven't had a good look over this code, but my first instinct is I wouldn't recommend use of ReassociateObjectWithSessionFilter because it deals with HttpSession and SecurityContextHolder integration, which is a high-risk area of code and normal user applications should integrate solely with the latter whilst relying on HttpSessionIntegrationFilter to deal with persistence between HTTP requests.

              Comment

              Working...
              X