Announcement Announcement Module
Collapse
No announcement yet.
Refresh Roles for Logged In User? Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Refresh Roles for Logged In User?

    Hello,

    We are using CAS authentication and Acegi 0.7.0-SNAPSHOT.

    We have a process that modifies the roles a logged in user can have. The modification is performed while a user is logged in.

    The user's Authentication is populated with their roles on a successful login. Is there a supported way to refresh this Authentication object, so that it will load up any new roles (GrantedAuthorities) it has gained?

    From what I can tell, not really, but hopefully there's something in there I haven't seen.

    Thanks very much!
    Seth

  • #2
    Here are my initial impressions on how to handle reloading of the Authentication object, transparently to the user, and while the user is logged in.

    It seems a simple way would be introduce a Session variable, such as RELOAD_AUTHENTICATION. Then, the next time the CasAuthenticationProvider runs (which is every request), it can look for that Session variable. If it finds it, it can run authenticateNow again. This will merely confirms the ticket and reload the roles via the AuthoritiesPopulator.

    So now my question is, any problems with this? Also, would this concept of "transparent re-auth" useful for the other types of Authentication Providers?

    I didn't want to fire a Spring event, or call a method directly, because I want to keep my Biz Code as free from Acegi, etc as possible. This at least keeps compile dependency free.

    Thoughts?

    Thanks,
    Seth

    Comment


    • #3
      If you were just using the standard DaoAuthenticationProvider (no CAS) it would be simple. Just evict the UserDetails from any UserCache, and next time they make a request the AuthenticationDao will be consulted for the current details.

      As you're using CAS, that means CasAuthenticationProvider. This provider responds to two types of Authentication objects: UsernamePasswordAuthenticationToken, which holds the CAS ticket string, and CasAuthenticationToken, which is generated in response to the user's first valid presentation of the UsernamePasswordAuthenticationToken and is used for the remainder of the user session. The CasAuthenticationToken is built by delegation to the CasAuthoritiesPopulator, with the typical implementation being DaoCasAuthoritiesPopulator. Unfortunately there isn't an easy way of doing what you're asking.

      Aside from not using CAS, the easiest way I can identify to achieve this is to modify CasAuthenticationProvider to always refresh the roles. But to be honest I think it will be getting very hard to follow what's happening at a maintenance level - it's deep enough at present.

      Is there a way you could utilise ACL security instead of modifying roles in real time?

      Comment


      • #4
        Aside from not using CAS, the easiest way I can identify to achieve this is to modify CasAuthenticationProvider to always refresh the roles.
        I thought about that, but I don't want to take the performance hit for every request. I think putting a simple token into the Session to indicate "Please refresh my Authentication object" will work.

        As for your ACL suggestion, I have not investigated that approach yet. I will definitely look into it.

        Thanks,
        Seth

        Comment


        • #5
          You could use a cache, and then introduce a network cache system like SwarmCache that uses multicast to evict expired objects. However, for all that effort I think the ACL model would be a lot more robust and flexible, and take less time as well (modifying CasAuthenticationProvider would probably introduce a maintenance overhead for you in the future, especially as CAS 3 is developed and changes are made).

          Comment


          • #6
            For now, I've introduced YAF (yet another Filter) that looks for one of a few conditions. If it finds one, it will reload the current CasAuthenticationToken (using the populator) and place it back into the ContextHolder. I put this after the HttpSessionIntegrationFilter, but before the actual Security Filter.

            Code:
            import java.io.IOException;
            import java.util.Iterator;
            import java.util.List;
            
            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.HttpSession;
            
            import net.sf.acegisecurity.Authentication;
            import net.sf.acegisecurity.UserDetails;
            import net.sf.acegisecurity.context.ContextHolder;
            import net.sf.acegisecurity.context.SecureContext;
            import net.sf.acegisecurity.providers.cas.CasAuthenticationToken;
            import net.sf.acegisecurity.providers.cas.CasAuthoritiesPopulator;
            
            import org.apache.commons.logging.Log;
            import org.apache.commons.logging.LogFactory;
            import org.springframework.util.PathMatcher;
            
            /**
             * Looks for a <code>ACEGI_REFRESH_AUTHORITIES</code> token
             * in the Session or in the Request.  Or, look for a known set of URIs.  If it exists, reload the authentication object,
             * and &#40;optinally&#41; clear the variable from the session.
             */
            public class RefreshableCasAuthenticationTokenFilter implements Filter &#123;
                
                public static final String REFRESH_AUTHORITIES = "ACEGI_REFRESH_AUTHORITIES";
                private static final Log log = LogFactory.getLog&#40;RefreshableCasAuthenticationTokenFilter.class&#41;;
                
                private String key;
                private CasAuthoritiesPopulator populator;
                private List watchedUris;
                
                public void setCasAuthoritiesPopulator&#40;CasAuthoritiesPopulator populator&#41; &#123;
                    this.populator = populator;
                &#125;
                
                public void setKey&#40;String key&#41; &#123;
                    this.key = key;
                &#125;
                
                public void setWatchedUris&#40;List watchedUris&#41; &#123;
                    this.watchedUris = watchedUris;
                &#125;
            
                /**
                 * @see javax.servlet.Filter#init&#40;javax.servlet.FilterConfig&#41;
                 */
                public void init&#40;FilterConfig arg0&#41; throws ServletException &#123; &#125;
            
                /**
                 * @see javax.servlet.Filter#doFilter&#40;javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain&#41;
                 */
                public void doFilter&#40;ServletRequest request, ServletResponse response,
                        FilterChain chain&#41; throws IOException, ServletException &#123;
                    HttpServletRequest httpRequest = &#40;HttpServletRequest&#41; request;
                    HttpSession session = httpRequest.getSession&#40;false&#41;;
                    
                    if &#40;&#40;session != null && session.getAttribute&#40;REFRESH_AUTHORITIES&#41; != null&#41; ||
                            request.getParameter&#40;REFRESH_AUTHORITIES&#41; != null ||
                            matchesPath&#40;httpRequest&#41;&#41; &#123;
                        log.debug&#40;"Refreshing the roles of the user"&#41;;
                        Authentication auth = &#40;&#40;SecureContext&#41;ContextHolder.getContext&#40;&#41;&#41;.getAuthentication&#40;&#41;;
                        CasAuthenticationToken token = &#40;CasAuthenticationToken&#41; auth;
                        UserDetails userDetails = populator.getUserDetails&#40;auth.getName&#40;&#41;&#41;;
                        token = new CasAuthenticationToken&#40;this.key, token.getName&#40;&#41;,
                                token.getCredentials&#40;&#41;, userDetails.getAuthorities&#40;&#41;,
                                userDetails, token.getProxyList&#40;&#41;,
                                token.getProxyGrantingTicketIou&#40;&#41;&#41;;
                        &#40;&#40;SecureContext&#41;ContextHolder.getContext&#40;&#41;&#41;.setAuthentication&#40;token&#41;;
                        
                        session.removeAttribute&#40;REFRESH_AUTHORITIES&#41;;
                    &#125;
                    
                    chain.doFilter&#40;request, response&#41;;
                &#125;
            
                /**
                 * @param request
                 * @return
                 */
                private boolean matchesPath&#40;HttpServletRequest request&#41; &#123;
                    if &#40;watchedUris == null || watchedUris.size&#40;&#41; <= 0&#41; &#123;
                        return false;
                    &#125;
                    
                    String pathInfo = request.getPathInfo&#40;&#41;;
                    String queryString = request.getQueryString&#40;&#41;;
            
                    String url = request.getServletPath&#40;&#41;
                        + &#40;&#40;pathInfo == null&#41; ? "" &#58; pathInfo&#41;
                        + &#40;&#40;queryString == null&#41; ? "" &#58; &#40;"?" + queryString&#41;&#41;;
                    
                    for &#40;Iterator i = watchedUris.iterator&#40;&#41;; i.hasNext&#40;&#41;;&#41; &#123;
                        String path = &#40;String&#41; i.next&#40;&#41;;
                        boolean matched = PathMatcher.match&#40;path, url&#41;;
                        
                        if &#40;log.isDebugEnabled&#40;&#41;&#41; &#123;
                            log.debug&#40;"Candidate is&#58; '" + url + "'; pattern is "
                                + path + "; matched=" + matched&#41;;
                        &#125;
                        
                        if &#40;matched&#41; &#123;
                            return true;
                        &#125;
                    &#125;
                    
                    return false;
                &#125;
            
                /**
                 * @see javax.servlet.Filter#destroy&#40;&#41;
                 */
                public void destroy&#40;&#41; &#123; &#125;
                
            &#125;
            I haven't investigated ACLs yet, but definitely will in the future.

            Comment

            Working...
            X