"Simple Software"

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

July 23rd, 2009 by Lincoln

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. Please feel free to post any suggestions or questions.

Sequence Diagram for LoginRedirectFilter Flow

Post to Twitter Post to Delicious Post to Digg Post to StumbleUpon

Posted in JSF, Spring

12 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. This was all taken out of:

        http://ocpsoft.com/scrumshark/ , 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~!

Leave a Comment




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

Search

Recent Posts

Recent Comments

OcpSoft Agile Management

Recent Tweets:

Posting tweet...

Sponsored By:

Resources

Meta