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> |
<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>
Need some /pretty /urls in your JSF web-app? Try PrettyFaces: URL-rewriting for Java EE and JSF. (Free and open-source!)
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;
} |
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
{}
} |
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> |
<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.
|
|
|
Want more info?
Try one of these highly-recommended books, written by Spring Developers and Spring Experts:
|
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.
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.
Hi Lincoln!
Here can I see the FacesUtils java code?
Thank’s a lot.
FacesUtils can be found here: http://code.google.com/p/ocpsoft-tools/source/browse/trunk/socialpm/socialpm-ui/src/main/java/com/ocpsoft/socialpm/jsf/FacesUtils.java
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
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/
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. 🙂
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.
BTW where can I download your HelloWorld example?
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!
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/
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~!
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
Redirecting back to the source URL comes out of the box with Spring Security 3 – see http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/SavedRequestAwareAuthenticationSuccessHandler.html
Hi Lincoln!
Can you provide a examples for enable or disable the redirect functionality use Facelets ui:param ?
Thank’s a lot.
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.
Hi Jo,
The methods you are looking for are now called:
Hope this helps!
~Lincoln
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?
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
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.
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