Announcement Announcement Module
Collapse
No announcement yet.
unprotected path with FilterSecurityInterceptor Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • unprotected path with FilterSecurityInterceptor

    I would like my login page to to be inside a protected directory hierarchy. For example, I want to protect /admin/*, but put the login page at /admin/login.jsp. My objectDefinitionSource might look like this:

    Code:
    /A/admin/login.jsp=NOT_PROTECTED
    \A/admin/.*/add=ROLE_ADMIN
    \A/admin/.*/edit=ROLE_ADMIN
    \A/admin/.*/delete=ROLE_ADMIN
    \A/admin/.*=ROLE_USER
    Is there a way to say "I don't care about SecureContext for /admin/login.jsp" ? I tried writing a voter to always allow it, but then it still needs a SecureContext (causing infinite redirects to the login page).

    It looks like it would be possible to provide a hook in AbstractSecurityInterceptor do it... good or bad idea?

    I know the obvious solution is to put everything protected deeper in the directory structure (/admin/secure/) but I would prefer a cleaner URL. Most importantly, I want /admin/ to be a (protected) page.

    Thanks!
    - Ryan

  • #2
    Sorry, I just found this post: http://forum.springframework.org/showthread.php?t=10725

    I assume these are still the recommended approaches? Does anyone have code they can share to support either of these methods?
    Last edited by robyn; May 14th, 2006, 06:27 PM.

    Comment


    • #3
      The post you referred to is still the current situation.

      A better way is the anonymous user approach, perhaps adding a new AnonymousAuthentication and AnonymousAuthenticationProvider to the framework so that the anonymous user can be properly handled. Acegi Security framework classes can then differentiate between a "real" user and an anonymous user and handle the 403 vs authentication entry point issue properly.

      Comment


      • #4
        Wow, I got it to work Thanks so much. This really is an amazing package!

        Here is my subclass of SecurityEnforcementFilter

        Code:
        /**
         * Overrides SecurityEnforcementFilter.sendAccessDeniedError. If a LoginAuthentication
         * was found, it is removed and the integration entry point is used to handle normal login.
         * Otherwise the accessDenied is a real error and control is returned to the superclass
         * @author rcarver
         */
        public class LoginAllowedSecurityEnforcementFilter extends SecurityEnforcementFilter {
            
            protected static final Log log = LogFactory.getLog(LoginAllowedSecurityEnforcementFilter.class);
            
            private AbstractIntegrationFilter integrationFilter;
            
            /**
             * @param integrationFilter The integrationFilter to set.
             */
            public void setIntegrationFilter(AbstractIntegrationFilter integrationFilter) {
                this.integrationFilter = integrationFilter;
            }
            
            public void afterPropertiesSet() throws Exception {
                if (this.integrationFilter == null) {
                    throw new IllegalArgumentException("integrationFilter must be defined");
                }
                super.afterPropertiesSet();
            }
            
            /**
             * @see net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter#sendAccessDeniedError(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
             */
            protected void sendAccessDeniedError(ServletRequest request, ServletResponse response) throws IOException {
                Authentication authentication = (Authentication) integrationFilter.extractFromContainer(request);
                
                log.debug("Handling access denied error");
                
                if (authentication instanceof LoginAuthentication) {
                    
                    log.debug("Found a LoginAuthentication, removing it from container");
                    integrationFilter.commitToContainer(request, null);
                    
                    log.debug("Redirecting to login entryPoint");
                    try {
                        getAuthenticationEntryPoint().commence(request, response);
                    } catch (IOException e) {
                        throw e;
                    } catch (ServletException e) {
                        log.debug("Problem using authenticationEntryPoint", e);
                    }
                    return;
                }
                
                super.sendAccessDeniedError(request, response);
            }
        }
        New ServletFilter to ensure a LoginAuthentication is available. This filter runs before other Acegi Security filters.

        Code:
        /**
         * ServletFilter to provides a LoginAuthentication if no authentication was found. This should run
         * before other Acegi Security filters.
         * @author rcarver
         */
        public class LoginAuthenticationPopulatingFilter implements Filter {
            
            protected static final Log log = LogFactory.getLog(LoginAllowedSecurityEnforcementFilter.class);
            
            private AbstractIntegrationFilter integrationFilter;
        
            /**
             * @param integrationFilter The integrationFilter to set.
             */
            public void setIntegrationFilter(AbstractIntegrationFilter integrationFilter) {
                this.integrationFilter = integrationFilter;
            }
        
            /**
             * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
             */
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
                Object auth = integrationFilter.extractFromContainer(request);
                if (auth == null) {
                    log.debug("No Authentication was found, providing a LoginAuthentication");
                    integrationFilter.commitToContainer(request, new LoginAuthentication());
                }
                filterChain.doFilter(request, response);
            }
        
            /**
             * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
             */
            public void init(FilterConfig arg0) throws ServletException { }
        
            /**
             * @see javax.servlet.Filter#destroy()
             */
            public void destroy() { }
        
        }
        LoginAuthentication is an extremely simple implementation of Authentication that is hardcoded with a single GrantedAuthority "ANONYMOUS_LOGIN"

        Then, I just added a LoginAuthenticationProvider (which always authenticates), and a new role voter to allow ANONYMOUS_*

        I think that's about it. Thanks again.

        Comment


        • #5
          Just one thing, you might prefer to query and update ContextHolder directly. If your LoginAuthenticationPopulatingFilter appears after all the other Acegi Security filters, Acegi Security will automatically handle updating the well-known location. Still, your approach (working directly with the well-known location) will work if your LoginAuthenticationPopulatingFilter appears before any Acegi Security filters, as they will copy the result of your filter from the well-known location to the ContextHolder.

          Comment


          • #6
            Hmm, ok I think I see what you're saying.

            I just found a small issue I may have created. I haven't looked into it much yet but if you can see what the cause is I would sure appreciate it:

            The "after login, return to the original page requested" functionality is not working anymore. I'm always taken to AuthenticationProcessingFilter.defaultTargetUrl

            Thanks

            Comment


            • #7
              The SecurityEnforcementCode which sets the redirect location is never called. You'll need to place equivalent lines in your sendAccessDeniedError:

              Code:
              ((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
                              targetUrl);

              Comment


              • #8
                Ok, that was obvious.. I apologize for not digging around first.

                I found and fixed this issue as well:

                If, while a user is logged in, their user is disabled or deleted, they will be infinitely redirected to the login page. This was because there was an Authentication in the SecureContext but it was invalid; my LoginAuthenticationPopulatingFilter never got a chance to replace it. I fixed this by adding
                Code:
                ((SecureContext) ContextHolder.getContext()).setAuthentication(null);
                to SecurityEnfocementFilter just before it calls
                Code:
                getAuthenticationEntryPoint().commence(request, response);
                . Should this be the default behavior or will it cause problems elsewhere?

                Here is the fix for target url:

                Code:
                    /**
                     * Copied and modified from SecurityEnforcementFilter. 
                     * Necessary for redirecting back to the requested url after authentication
                     * @param request
                     */
                    private void setTargetUrl(ServletRequest request) {            
                        HttpServletRequest httpRequest = (HttpServletRequest) request;
                
                        int port = getPortResolver().getServerPort(request);
                        boolean includePort = true;
                
                        if ("http".equals(request.getScheme().toLowerCase())
                            && (port == 80)) {
                            includePort = false;
                        }
                
                        if ("https".equals(request.getScheme().toLowerCase())
                            && (port == 443)) {
                            includePort = false;
                        }
                
                        String qs = httpRequest.getQueryString();
                        String pathInfo = httpRequest.getPathInfo();
                        String targetUrl = request.getScheme() + "://"
                            + request.getServerName() + ((includePort) ? (":" + port) : "")
                            + httpRequest.getContextPath()
                            + httpRequest.getServletPath()
                            + ((pathInfo == null) ? "" : pathInfo)
                            + ((qs != null) ? ("?" + qs) : "");
                
                        httpRequest.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY, targetUrl);
                    }
                It gets called in sendAccessDeniedError(..)

                Code:
                                setTargetUrl(request);
                                getAuthenticationEntryPoint().commence(request, response);

                Comment


                • #9
                  Yes, as mentioned earlier you should use ContextHolder instead of HttpSession. Leave the integration with HttpSession to the HttpSessionIntegrationFilter. Although I see no problems with your current approach, as you're ending up updating ContextHolder as recommended.

                  Comment

                  Working...
                  X