October 09 2008
Acegi/Spring Security Integration - JSF Login Page
Tutorials - What a nightmare
Everyone seems to be going through hell to get a fully functional JSF login page working with Spring Security (formerly Acegi,) and yes, I did too, but there’s an EASY way to make this happen. And get this:
- It takes just five clear and well written lines of Java code.
First, the solution. Afterwards, the dirty details. (Spring 2.5.2 was used for this example.)
You can find a downloadable working example here.
The Solution:
public class LoginBean { //managed properties for the login page, username/password/etc... // This is the action method called when the user clicks the "login" button public String doLogin() throws IOException, ServletException { ExternalContext context = FacesContext.getCurrentInstance().getExternalContext(); RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()) .getRequestDispatcher("/j_spring_security_check"); dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse()); FacesContext.getCurrentInstance().responseComplete(); // It's OK to return null here because Faces is just going to exit. return null; } }
—-
For anyone who was struggling because Spring Security requires you to use a Filter to intercept the login postback, thus either preventing you from being able to do JSF style validation, or visa-versa, creating a scenario where JSF can process results, but blocks Acegi from processing the request parameters.
Simply use an HttpRequestDispatcher to allow both JSf and Spring Security to function one after another. JSF goes first, then delegates work to a Spring Security (thus preserving any request parameters that Spring Security is looking for.) After forwarding, tell JSF you have finished, and not to do any more work, immediately stop processing.
If the login credentials were bad, redirect to the Login page. If the credentials were good, redirect to the requested URL. You can even show a dynamic message for bad credentials. Add the following method to your LoginBean in order to extract any login errors, and display a message to the user:
@PostConstruct
private void handleErrorMessage()
{
Exception e = (Exception) FacesUtils
.getSessionAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY);
if (e instanceof BadCredentialsException)
{
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, null);
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Username or password not valid.", null));
}
}web.xml
You must configure your Spring Security Filter Chain to process Servlet FORWARD as well as REQUESTs.
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping>
—-
applicationContext-security.xml
As for the Spring Security configuration, everything can be left pretty standard. The relevant parts of my configuration, for example:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd"> <global-method-security secured-annotations="enabled"> </global-method-security> <http auto-config="true" access-denied-page="/accessDenied.jsp"> <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /> <form-login login-processing-url="/j_spring_security_check" login-page="/login" default-target-url="/" authentication-failure-url="/login" /> <logout logout-url="/logout" logout-success-url="/" /> </http> <authentication-provider> <!-- Your authentication provider here (example below)--> </authentication-provider> </beans:beans>
—-
Notice here that the “login-processing-url” is set to “/j_spring_security_check”, which is the location where our HttpRequestDispatcher is going to forward to. You can call this whatever you want, but the two must match exactly.
login.xhtml / login.jspx / login.jsp
(Whatever you use as your JSF page content type, take your pick.)
So the last part of the puzzle is relatively easy. You need a JSF login page that conforms to Spring Security’s parameter naming requirements. When this page submits, its values will be forwarded to the Spring Security Filter Chain.
Notice that you don’t even need to tie the input field values to a JSF backing bean! The values only need to be intercepted by Spring Security on forward. However, if you want to do all that cool validation and stuff that JSF lets you do… go for it. I just wanted to save space in the article, and prove a point that it’s not needed.
<?xml version="1.0" encoding="ISO-8859-1" ?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" version="2.0"> <f:view> <h:form id="loginForm" prependId="false"> <label for="j_username"><h:outputText value="Username:" /><br /> </label> <h:inputText id="j_username" required="true"> </h:inputText> <br /> <br /> <label for="j_password"><h:outputText value="Password:" /><br /> </label> <h:inputSecret id="j_password" required="true"> </h:inputSecret> <br /> <br /> <label for="_spring_security_remember_me"> <h:outputText value="Remember me" /> </label> <h:selectBooleanCheckbox id="_spring_security_remember_me" /> <br /> <h:commandButton type="submit" id="login" action="#{loginBean.doLogin}" value="Login" /> </h:form> </f:view> </jsp:root>
Remember that Spring Security is expecting parameters to be named as they are in this file. j_username, j_password, _spring_security_remember_me. Don’t change these ids unless you change your Spring configuration.
—-
If you’re having problems
Add a LoggerListener to allow Spring Security to print messages to your logging output. This will allow you to view any error messages that may be occurring. (Note: This should be copied verbatim)
<bean id="loggerListener"
class="org.springframework.security.event.authentication.LoggerListener" />—-
Finished
And that’s all it took. A simple forward to a new servlet. No JSF navigation cases, no extra configuration. Just a little J2EE, and a night of no sleep. I hope this helps a LOT of people who seem to be struggling with the task of integrating these two excellent frameworks.
Considering the forces of this problem, we really required almost no invasiveness in our normal application logic. JSF does its validation and processing without being impacted by Acegi, and Acegi can perform its magic authentication without knowing that JSF was ever the provider of its parameters.
You can see a working example of this guide here.
Enjoy.
Example Authentication Provider for Testing
<!-- Usernames/Passwords are rod/koala dianne/emu scott/wombat peter/opal --> <authentication-provider> <password-encoder hash="md5" /> <user-service> <user name="rod" password="a564de63c2d0da68cf47586ee05984d7" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" /> <user name="dianne" password="65d15fe9156f9c4bbffd98085992a44e" authorities="ROLE_USER,ROLE_TELLER" /> <user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" /> <user name="peter" password="22b5c9accc6e1ba628cedc63a72d57f8" authorities="ROLE_USER" /> </user-service> </authentication-provider>

Nice one.
Though, the five lines of Java code can be reduced to three or four lines if you used ExternalContext#dispatch()
Presently I’m using a custom security realm within Glassfish for authentication. Being very green behind the ears to JSF and Spring how would that fit into the authentication-provider model? Would it be possible to see a full example of your solution? Way cool stuff!
Best post on this topic I’ve seen! Any way of getting a little more of your sample code? I’m working on catching the BadCredentialsException and displaying per instructions above but don’t think I have things set up quite right.
What else would you like to see in particular? Send me an email with your scenario.
The BadCredentialsException is actually not caught, but is saved in the HttpSession object by Spring Security (which is why you get it out with a key.) So whenever your Login page is about to render, you need to check for that stored Exception object and handle your processing before the render response phase.
I did this using a “request” scoped LoginBean, so that a new bean is created every request. Since JSF is invoked after Spring Security processes any submissions, the Exception object will already be populated by the time the LoginBean is being constructed by JSF.
This being known, you can use the @PostConstruct annotation (see my example above) in order to do the Exception processing, and add a FacesMessage to the queue.
These FacesMessage objects will not be displayed unless you place a <h:messages globalOnly=”true”/> tag somewhere on your page.
Does this help?
I’ve posted an example AuthenticationProvider that I got from another tutorial site. (See above)
Hello,
if I have clicked to button on login page nothing happend. I wasnt redirect nowhere? Can you publish all source code for this example please?
Moreover I can´t debug method doLogin in LoginBean cause application skip this part.
I can’t publish the full source, but it sounds like your JSF LoginBean action method is either not being called, or not doing the forward. But if it is, make sure that you have properly configured your Spring Security to intercept the “login-processing-url” which is being forwarded to.
Let me know if this helped solve your problem, or if you could post more details that would be great.
This post is a good basic spring-security intro:
http://www.mularien.com/blog/2008/07/07/5-minute-guide-to-spring-security/
If your doLogin() method is not being called. Make sure that you have properly configured your LoginBean with faces-config.xml, and also make sure that your EL expression in your JSP matches the name of the method, eg: action=”#{loginBean.doLogin}”
So, LoginBean seems to be working for now. But there is other issue. From login page I am everytime redirect to failure-url.
My applicationContext-security.xml configuration:
This probably means that your authentication is failing. See above for how to add a LoggerListener to see what Spring Security is doing/complaining about. You’ll need to have a logger set up. (It’s added right above the “Finished” section.)
I know, where is problem, but I don´t know why.:-)
I have h:inputText id=”j_username” and h:inputText id=”j_password”. But it seems that values from these fields are not propagated to processing.
Exception: WARNING: Authentication event AuthenticationFailureBadCredentialsEvent: ; details: org.springframework.security.ui.WebAuthenticationDetails@0: RemoteIpAddress: 10.0.0.2; SessionId: 2e1062b9cf6cdd18dcbec0710981641ee69527df73809aad3aa757501920f86f; exception: Bad credentials
As you can see, credentials fields are empty.
That exception is also thrown if the username/password are incorrect.
Username/Password is correct
I am now sure, that values of j_username and j_password are empty.
I have implemented UserDetailsService and value of login for method public UserDetails loadUserByUsername(String login) is everytime empty.
Do you have any idea why values are not initialized?
We’re working on creating a project file that will demonstrate a full working example. It should be coming in a few days.
I’m new to this…
I’m using Spring / Spring Security / Orchestra / Eclipselink. While I have the general spring security login page working I have tried your example but when I run the app I get java.lang.RuntimeException: Cannot find FacesContext.
I tried using the loggerListener but nothing is printed out.
What am I doing wrong?
Ok I tried something else…
In my web.xml the faces servlet is mapped to /faces/* so I modified my springContext login-page entry under form-login to be “/faces/Login.jspx”.
Using IE7 when I run the app it appears that an attempt to redirect gets stuck in a loop … in the bottom left hand corner of IE7 “Redirecting to site: … Login.jspx” flashes rapidly but the Login.jspx page is never displayed.
I got the example working and if I try to access a secured page via the browser address text field it opens the login page. But when I try to enter the secured page via the faces-config.xml navigation rules, it doesn’t matter if I am logged in or not, I always have access. Is this the intended behavior? Are navigation rules outside the control of Spring Security?
Yes, unless you do a redirect in your navigation case. Spring security is based around Servlet Filters, so JSF navigation, without redirect, doesn’t leave the servlet until after the page is rendered. You should be able to fix this by adding the redirect property to the navigation cases in your faces-config.xml file.
Thanks Derek. I am new to JSF so I hope you do not mind me asking if doing redirects all the time for security reasons is advisable (e.g. what about performance?)?
[...] gotten a good number of comments from Lincoln’s latest post on Spring Security and JSF. A few comments have asked for further code samples on how to get this example working. We [...]
Derek finished his working example. You can download the source and run it here:
http://ocpsoft.com/java/acegi-spring-security-jsf-integration-project-continued/
I am now able to display the login page but when I click the login during a debug session and step into the doLogin method when I reach the dispatcher.forward… statement I get javax.faces.el.EvaluatorException: java.lang.NullPointerException. I have checked my code against Derek’s example… any ideas what could be causing this?
Credentials fields j_username and j_passwords are not being passed to the backend for proecessing just like Dave says above.
Does anyone have a solution to this problem?
I am using NetBeans 6.1 and Tomcat 6.0.14
Thanks
Josh.
Josh and Dave…
I had the same problem using MyFaces 1.2.2. The problem was that due to a bug the j_username and j_pasword fields were prepended by “null:” although the prependId=false.
I upgraded to MyFaces 1.2.4 and all is fine now.
Has anyone tried the MyFaces Tomahawk library inputText tag’s “forceId” property which is supposed to ensure expected IDs are in the Acegi filter? I want to try this later today…
Hi,
how can i get and show stored Username (when user chose “remember me” option). Im new in Acegi
thanks in advance
hi,
when i chose “remember me”, and then i click on log out, i can although access secret page (without username and password)
when can i really log out?
thanks
Has anyone gotten this example to work as one would expect, specifically the ‘protected’ page being intercepted properly?
No. Haven’t got it working yet. I also posted at Spring Community Forums hoping for additional help.
I haven’t figured this out yet either. I can get things to successfully authenticate (against a test user-service), but the forward call is not working. If I return null from doLogin, I remain at the login page. If I return my normal ’success’ String and let jsf handle navigation via a redirect, I get an IllegalStateException. If I remove the redirect, no navigation. What jsf impl/version are people using? I’m trying MyFaces 1.1 / Trinidad.
Shane, try to set the once-per-request parameter to false in the http tag, like this:
This fixed the forwarding problem for me.