Categories

Duncan Mills

Syndicate this blog

J2EE Security - A JSF based Login Form

Warning: This is quite an old posting - about 2 years old now. It does work and has been used successfully by many people. Some folks, however, seem to have problems with it and much as I'd love to spend time looking at their web descriptors I don't have that time to spend. So if this does not work for you just check everything carefully. This is an idea not a product. Ideally of course the JCP would sort out this whole issue and we'd have a generic way of working with container security and JSF without having to use hacks like this or verbatim tags. Now read on...



I've been over the problems with trying to create a J2EE login form
with JSF before and had pretty much decided that it was simply not
possible to do in a clean way without <f:verbatim> tags.
Well I've been looking at the problem again and have changed
my thinking a little and I've come up with a (not too hacky)
solution.
Let me recap the issue here. The various Form components available in
JSF will not let you specify the target action, everything is a
post-back. When using container security, however, you have to
specifically submit to the magic action j_security_check.
This means that the only way to do this in a JSF page is to use an HTML
form tag enclosed in verbatim tags. This has the side effect that the
post is not handled by JSF at all meaning you can't take advantage of
normal JSF functionality such as validators, plus you have a horrible
chimera of a page containing both markup and components. This screws up
things like skinning.
 In a flash of inspiration it occurred to me that I could
build the whole login page as a pure JSF page and avoid the whole
problem of having to use verbatim tags at all. Instead the JSF page
would simply gather the input for the username and password and would
pass that on to a proxy to do the actual submit for me. I'd tried
something similar before programmatically with no luck, but this time
I'm using a JSPX (not JSF) page as the proxy. Let's look at the
bits

The Main Login Page

The main login is a normal JSF page at it's most basic it will need two
fields and a commandButton to "logon" so a simple example (excluding
headers) will look a little like this:

<f:view>
  <afh:html>
    <afh:head title="login">
      <meta
http-equiv="Content-Type"
           
content="text/html; charset=windows-1252"/>
    </afh:head>
    <afh:body>
     
<h:form>
       
<af:panelForm>
         
<af:inputText label="User Name:"
           
           
id="j_username"/>
         
<af:inputText label="Password:"
           
           
id="j_password"
           
           
secret="true"/>
         
<f:facet name="footer">
           
<af:panelButtonBar>
             
<af:commandButton text="Logon"
           
           
       
action="#{Login.loginAction}"/>
           
</af:panelButtonBar>
         
</f:facet>
       
</af:panelForm>
     
</h:form>
    </afh:body>
  </afh:html>
</f:view> 
The key things here are the setting of the ID attributes of
the input fields to ensure that the correct names for the values are
used on the POST, and the action defined for the
commandButton. 

The loginAction

The commandButton references an action in a request scope
managed bean. This action is pretty simple:
public String loginAction() throws IOException,
ServletException {
  ExternalContext ectx =
FacesContext.getCurrentInstance().getExternalContext();
  HttpServletRequest request =
(HttpServletRequest)ectx.getRequest();
  HttpServletResponse response =
(HttpServletResponse)ectx.getResponse();
  RequestDispatcher dispatcher =
request.getRequestDispatcher("loginProxy.jspx");
  dispatcher.forward(request,response);
  return null;
}

The code basically issues a forward to the page
"loginProxy.jsp", note that by the time this code executes, things like
validators on the main login page will have a chance to be
processed. 
As a side note it did occur to me to dispatch direct to
j_security_check at this point - however, in OC4J certainly the
getRequestDispatcher() call will return null for j_security_check, so
that was a non starter. If you use a different app server give that a
go first - before you try the proxy page - it might just work.

The loginProxy

The proxy page is a plain JSPX (although it can also be JSP as
well) that actually has not visible
UI. It re-maps the values for username and password off the request and
auto-submits to j_security_check:
<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
version="2.0">
  <jsp:output omit-xml-declaration="true"
doctype-root-element="HTML"
             
doctype-system="http://www.w3.org/TR/html4/loose.dtd"
             
doctype-public="-W3CDTD HTML 4.01 Transitional//EN"/>
  <jsp:directive.page
contentType="text/html;charset=windows-1252"/>
  <html>
    <head>
      <meta
http-equiv="Content-Type"
           
content="text/html; charset=windows-1252"/>
     
<title>Logging in</title>
    </head>
      <body
onload="document.forms[0].submit()">
      <form
method="post" action="j_security_check">
       
<input type="hidden" name="j_username"
value="${param.j_username}"/>
       
<input type="hidden" name="j_password"
value="${param.j_password}"/>
      </form>
    </body>
  </html>
</jsp:root>

So there you go - this technique seems to work well. The only
problem is that the browser will flash to a blank page when you forward
to the proxyPage, however, this is not so bad and you could dress that
page with a friendly message if you really need to. On the up side,
using this technique will mean that the login fields and button can be
happily mixed onto any JSF page and you can take full advantage of JSF
for pre-processing before the you forward to the proxy. For instance if
you wanted to add a "Remember Me" checkbox into the login you can now
do it.

Comments:

Comment from: Justino [Visitor]
what about the web.xml!? What configuration needs, becausa your idea is good, but the web.xml if you configurate the jsf as you login page, it wouldnt let you, because is a jsf
Permalink 10/11/06 @ 20:52
Comment from: John De Binder [Visitor]
Not a great solution. If you have to redirect to a plain JSP then you actually have inserted two unnecessary submits. The goal is to submit directly to the authentication procedure to reduce any security hole AND to keep from hurting performance. With my customer this solution would violate two absolute requirements: 1. Every submit with the userid and password must be https, while the anonymous portion of the site (in this case a Portal) is not. 2. Security team does not even want the userid and password to be passed to the application at all.
Permalink 04/12/06 @ 20:30
Comment from: R. LOPES [Visitor]
Could you please post your project ?
For me it just doesn't work (using OC4J). Because using a JSF page as a login page leads to error like the 'no skin' one.
Maybe something in the web.xml we miss.

Thanks.
Permalink 06/12/06 @ 03:33
Comment from: R. LOPES [Visitor]
Hi,

Once again it is not working.
Something really depressing here on the very few blogs talking about JDeveloper and ADF, and the Oracle forums, is that in 90%+ of the case you never get an answer to your problems, sometimes untested not working 'solutions', and a very very few times a working solution.

If Oracle wants to promote its framework, it has to boost the number of tutorials, examples and support.
Googling about ADF is most of the time a non sense since you always land on the same 3 blogs and the Oracle forums.
Proof of non use by the community ?
If you try on www.indeed.com to find statistics about JDeveloper or ADF jobs you get another frightening proof.

This is sad, since the ADF framework and JDeveloper look good and are worth of a more widespread usage. But marketing and support are way way far behind other commercial and open-source solutions.

Have a nice day.
Permalink 17/12/06 @ 21:46
Comment from: awattar [Visitor] · http://www.wees.pl
EXAMPLE
RoleManager manager = (RoleManager)new InitialContext().lookup("java:comp/RoleManager");
manager.login( username, pass );


DOC
http://www.orionserver.com/docs/api/orion/com/evermind/security/RoleManager.html#login(java.lang.String,%20java.lang.String)
Permalink 28/02/07 @ 21:51
Comment from: Tony [Visitor] · http://myurl.coma.de
Id does not work for me :(
Permalink 24/04/07 @ 11:14
Comment from: Anit Singh [Visitor]
I want to call a action on the load of JSF. How i will able to do that
Permalink 30/04/07 @ 09:23
Comment from: tush [Visitor] · http://www.myblog.com
i am trying to set Cookies in jsf try to add cookies in the response object but when i am trying to get cookies in request object, i am getting null .. is it too tuff to implement cookies in jsf ?
Permalink 14/05/07 @ 14:20
Comment from: Duncan Mills [Member]
No Cookies are easy you just use the #{cookie} el scope to access the cookie from the page, or for that matter from code.
Permalink 14/05/07 @ 15:47
Comment from: Vishwananth [Visitor]
Its good, but am looking for LDAP authentication, please do help me to get some idea on LDAP authentication java.
Permalink 08/08/07 @ 07:32
Comment from: Andy [Visitor]
Hi,

I tried with Apache Tomcat 6.0 and MyFaces 1.2 and had to do the following things:

in login.jsf:


Please log on.






in Login.java:

public String loginAction() throws IOException, ServletException {
ExternalContext ectx = FacesContext.getCurrentInstance()
.getExternalContext();
HttpServletRequest request = (HttpServletRequest) ectx.getRequest();
HttpServletResponse response = (HttpServletResponse) ectx.getResponse();
RequestDispatcher dispatcher = request.getRequestDispatcher("loginProxy.jspx");
request.setAttribute("j_username", getUserName());
request.setAttribute("j_password", getPassword());
dispatcher.forward(request, response);
return null;
}


in loginProxy.jspx:




Permalink 27/11/07 @ 22:33
Comment from: Andy [Visitor]
Sorry for that, but I had to replace the less-than and greater-than signs:

in login.jsf:

<h:form>
Please log on.
<h:inputText id="j_username" value="#{login.userName}" />
<h:inputSecret id="j_password" value="#{login.password}" />
<h:commandButton value="Logon" action="#{login.loginAction}" />
</h:form>



in loginProxy.jspx:

<body onload="document.forms[0].submit()">
<form method="post" action="j_security_check"><input
type="hidden" name="j_username" value="${j_username}" /> <input
type="hidden" name="j_password" value="${j_password}" /></form>
</body>
Permalink 27/11/07 @ 22:37
Comment from: shiva [Visitor]
i am using acegi security with JSF, i get this error

No exception of type AuthenticationException can be thrown; an exception type must be a
subclass of Throwable
Permalink 21/12/07 @ 10:59
Comment from: shiva [Visitor]
i am using acegi security with JSF, i get this error

No exception of type AuthenticationException can be thrown; an exception type must be a subclass of throwable, can u help me?. the code is below...

Authentication auth;
try {
auth = getAuthenticationManager().authenticate( authReq );
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Permalink 21/12/07 @ 11:01
Comment from: Marcel [Visitor]
I tried your solution but i got always this exception:

javax.servlet.ServletException: Cannot forward after response has been committed
javax.faces.webapp.FacesServlet.service(FacesServlet.java:154)

whats wrong?
Permalink 18/01/08 @ 14:18
Comment from: denigi [Visitor] · http://floreshti.com
thanks, I found what I need
Permalink 16/02/08 @ 12:59

Comments are closed for this post.