July 23rd, 2009 by Lincoln Baxter III

Spring Security – What happens after /you/ log in?

So you’ve got Spring Security up and running. Great! Now you’ve got a login page, and you just added a form on the global page menu to allow users to Login from any public page. There’s just one problem. When they log-in from a public page, they’re redirected to the default-login-url! Your users will have to re-navigate to the page they were already viewing when they logged in, or maybe they’ll just use the much dreaded “Back” button. That’s not a good interaction, but we have a solution. UPDATE: There is a simpler, but less complete solution built in. (See here.) This means appending “?spring-security-redirect=/your/target/url” to your redirect to the Spring Security Filter chain. If you have not already completed integrating Spring Security and JSF, please consider it, as this article depends on having a working JSF login page and managed bean.   Note: This approach will not work if you are invalidating/re-creating the session after a successful authentication (see Session Fixation attacks). Supporting session invalidation would take some extra work that will not be in the scope of this article.

The login form

Here is a basic JSF/Spring Security login form. It would be nice if we could enable or disable the redirect functionality, so we’ll add a hidden form field that is only rendered on demand (here we use Facelets ui:param functionality for our on-off switch.)  
<h:form prependId="false">
    <c:if test="#{redirect == 'true'}">
        <input type="hidden" name="redirect" value="true" />
    </c:if>
    
    <label for="j_username"><h:outputText value="Username:" /><ocp:message
        for="j_username" /><br />
    </label>
    <h:inputText id="j_username" value="#{loginBean.username}"
        required="true" />
 
    <br />
    <br />
    <label for="j_password"><h:outputText value="Password:" /><ocp:message
        for="j_password" /><br />
    </label>
    <h:inputSecret id="j_password" value="#{loginBean.password}"
        required="true" />
 
    <br />
    <br />
    <label for="_spring_security_remember_me"><h:outputText
        value="Remember me" /> </label>
    <h:selectBooleanCheckbox id="_spring_security_remember_me"
        value="#{loginBean.rememberMe}" />
    <br />
 
    <h:commandButton type="submit" id="login"
        action="#{loginBean.doLogin}" value="Login" />
 
    <h:commandButton type="submit" styleClass="faded" id="cancel"
        action="#{loginBean.doCancel}" value="Cancel" immediate="true" />
    <br />
    <br />
</h:form>

The login action method

First, check to make sure that we actually want to do a redirect after login. Do this by testing for the existence of our hidden form parameter.   Find the full LoginBean code here.
public String doLogin() throws IOException, ServletException
{
    String redirect = FacesUtils.getRequestParameter("redirect");
    if ((redirect != null) && !redirect.isEmpty())
    {
        redirect = PrettyContext.getCurrentInstance().getOriginalRequestUrl();
        Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
        sessionMap.put(LoginRedirectFilter.LAST_URL_REDIRECT_KEY, redirect);
    }
    FacesUtils.getExternalContext().dispatch("/j_spring_security_check");
    FacesUtils.getFacesContext().responseComplete();
    return null;
}
  Before forwarding to the Spring Security /j_security_login_check intercepting filter chain, we’ll need to set the current URL into a Session attribute: “LoginRedirectFilter.LAST_URL_REDIRECT_KEY”.   This will be used in our custom filter after the user successfully authenticates with Spring Security.

The login filter

Here is where we’ll check for the existence of our session attribute: “LAST_URL_REDIRECT_KEY”. If this key contains a value, then we should redirect the user to that URL. If the key does not contain a value, then we should not perform any redirect, and continue as normal.   One other concern is: what if authentication failed? Let’s assume that Spring Security will redirect the user to the Login Page if they send invalid credentials. We don’t want to trigger a redirect as they try to access the login page, so we also check to make sure we have a successfully authenticated user before redirecting.  
import java.io.IOException;
 
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 javax.servlet.http.HttpSession;
 
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
 
@Component
public class LoginRedirectFilter implements Filter
{
    public static final String LAST_URL_REDIRECT_KEY = LoginRedirectFilter.class.getName() + "LAST_URL_REDIRECT_KEY";
 
    @Override
    public void destroy()
    {}
 
    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
            throws IOException, ServletException
    {
        HttpSession session = ((HttpServletRequest) request).getSession();
        String redirectUrl = (String) session.getAttribute(LAST_URL_REDIRECT_KEY);
        if (isAuthenticated() && (redirectUrl != null) && !redirectUrl.isEmpty())
        {
            session.removeAttribute(LAST_URL_REDIRECT_KEY);
            HttpServletResponse resp = (HttpServletResponse) response;
            resp.sendRedirect(redirectUrl);
        }
        else
        {
            chain.doFilter(request, response);
        }
    }
 
    private boolean isAuthenticated()
    {
        boolean result = false;
        SecurityContext context = SecurityContextHolder.getContext();
        if (context instanceof SecurityContext)
        {
            Authentication authentication = context.getAuthentication();
            if (authentication instanceof AnonymousAuthenticationToken)
            {
                // not authenticated
            }
            else if (authentication instanceof Authentication)
            {
                result = true;
            }
        }
        return result;
    }
 
    @Override
    public void init(final FilterConfig filterConfig) throws ServletException
    {}
}
 

Web.xml

Some specific configuration is required to ensure the proper ordering of our filters. LoginRedirectFilter’s filter-mapping must be placed after any Spring Security filters – otherwise we will redirect too soon, and authentication will never occur. You probably want to place it before any filters that apply business logic.
<filter>
    <filter-name>loginRedirectFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>loginRedirectFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Putting it all together

This sequence diagram describes the entire process, including what Spring Security will be doing after intercepting the /j_security_check forwarded from LoginBean: You should now have a functional LoginRedirectFilter configured in tandem with Spring Security.

Sequence Diagram for LoginRedirectFilter Flow

Want more info?

Try one of these highly-recommended books, written by Spring Developers and Spring Experts:
Need more info?

Try one of these highly-recommended books:

Posted in JSF, Spring

21 Comments

  1. alvin says:

    I am not sure why you need to do this because this comes out of the box with spring-security. If spring detects an access to the secured resource it will automatically redirect to the login page storing the original request URL in the session – i.e. what you have done manually.

  2. Lincoln says:

    This article does not describe how to redirect after attempting to access a secure URL — it describes how to authenticate from a PUBLIC URL and send the user back to the page they were viewing, instead of being sent to the default-login-url.

  3. Marcos Berwanger says:

    Hi Lincoln!

    Here can I see the FacesUtils java code?

    Thank’s a lot.

  4. Pamela says:

    Hi Lincoln,

    Is there a way you can hide/protect URLs after a user logs in depending on user’s credentials or characteristics?

    If you have an example, that’d be great.

    Thanks

    1. Lincoln says:

      I usually defer to the non-filter security mechanisms for such use-cases. PrettyFaces allows you to do just that with URL actions. In the action you would check the user’s credentials / business scenario and decide whether or not to continue to process and render, or redirect to a different page. There are other ways, but… this is one.

      http://ocpsoft.com/prettyfaces/

  5. Pamela says:

    How about when I want to hide certain links/buttons for certain users? Would I go thru a similar process, or something different.

    Sorry, I’m fairly new to this whole programming in Spring Security.

    Thanks for your response, Lincoln. 🙂

    1. Lincoln says:

      No problem. You’d do something very similar. You’d want to create a JSF Action Method in a JSF Managed Bean – something like:

      public class MyBean {
      public boolean isUserGrantedAccess()
      {…}
      }

      This method would check the logged in user’s preferences, permissions, the state of the system or whatever logic needed.

      Then you would disable or render the link by referencing that action method in your Facelet view.

      <h:commandButton … rendered="#{myBean.userGrantedAccess}"/>

      Notice the “is” should be omitted. See “JEE Unified Expression Language Syntax” for more details on that.

      1. Pamela says:

        BTW where can I download your HelloWorld example?

      2. Pamela says:

        By Hello World, I meant your Basic Login example presented in this article and the previous one.

        Sorry if I confused you in any way.

        Thanks!

      3. Lincoln says:

        Unfortunately, there is no example, sorry. But you can download the sources for that and take a look.

        http://code.google.com/p/ocpsoft-tools/

  6. Pamela says:

    Do you think it’s possible to utilize taglibs/tld file in order to create URL protection?

    I’m currently using JSF 1.2 so I can utilize a tld file that would have security tag defined.

    If you could give me a quick example, that’d really be fabulous.

    Thanks~!

  7. Marcos Berwanger says:

    How can I get the logged user object on the “doLogin” bean method?

    Or exists another alternative for use rendered=”#{myBean.userGrantedAccess}

    Thank’s

    Marcos

  8. paomo says:

    Hi Lincoln!
    Can you provide a examples for enable or disable the redirect functionality use Facelets ui:param ?
    Thank’s a lot.

  9. jo says:

    I am wondering what this is (in LoginBean): PrettyContext.getCurrentInstance().getOriginalRequestUrl();
    I guess it is part of PrettyFaces, but it seems that is no longer in the API. I’m using 3.2.0 of PrettyFaces and can’t find it.

    1. Hi Jo,

      The methods you are looking for are now called:

      PrettyContext.getRequestURL()
      PrettyContext.getRequestQueryString()

      Hope this helps!
      ~Lincoln

  10. sudhish says:

    hello,
    Using your code sample and SpringSecurity 3.0.5, I cant seem to get access denied to work correctly. I seem to be getting this JSPG0036E: Failed to find resource /faces/protected.jsf.
    Any ideas?

  11. rayoman says:

    Hi i’m new to Spring Security, …
    do you know how do I detect if the user currently have an active session so that
    I can display a warning message and provide option for user to open a new session
    or close the current active session. Please advise

  12. Krishna says:

    hi Lincoln! Though you might have posted it long time back and you might not even remember the context but just a question. Does it work for the url with hash(#)? like http://www.foo.com/#/network somthing like this where i need to navigate to the sub nav bar of foo.com page.

  13. Hi Krishna,

    You would need to implement something like that yourself. I believe there is some stuff coming from Jboss about that soon, but nothing yet.

    ~Lincoln

Reply to alvin




Please note: In order to submit code or special characters, wrap it in

[code lang="xml"][/code]
(for your language) - or your tags will be eaten.

Please note: Comment moderation is enabled and may delay your comment from appearing. There is no need to resubmit your comment.