Servlet 3.0 containers, such as Glassfish 3, support easy programmatic login that allow more control over the login process.
A simple example could be when user authenticates and one should decide where to redirect user depending on some preconditions or if you need to fetch some data from database for authenticated user before redirecting to secured pages.
This example just builds on top of the example from the JDBC security realm with glassfish jsf.
We will change login form (login.xhtml) so that no longer has to contain mandatory fields j_username, j_password and action attribute j_security_check previously required by container. Login form is now a regular JSF form with user defined input components and login command button with action method attached.
<h:body> <p>Login to access secure pages:</p> <h:messages /> <h:form id="loginForm"> <h:panelGrid columns="2"> <h:outputLabel for="username" value="Username:" /> <h:inputText id="username" value="#{authBackingBean.username}" /> <h:outputLabel for="password" value="Password:" /> <h:inputSecret id="password" value="#{authBackingBean.password}" /> <h:commandButton id="loginButton" value="Login" action="#{authBackingBean.login}" /> </h:panelGrid> </h:form> </h:body>
To AuthBackingBean we just add login action method that will handle authentication and authorization. Username and password bean properties will hold user entered values after form is submited.
@ManagedBean @RequestScoped public class AuthBackingBean { private static Logger log = Logger.getLogger(AuthBackingBean.class.getName()); private String username; private String password; public String login() { FacesContext context = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest) context .getExternalContext().getRequest(); try { request.login(username, password); } catch (ServletException e) { context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN, "Login failed!", null)); return "login"; } //you can fetch user from database for authenticated principal and do some action Principal principal = request.getUserPrincipal(); log.info("Authenticated user: " + principal.getName()); if(request.isUserInRole("ADMIN")) { return "/admins/admins?faces-redirect=true"; } else { return "/users/users?faces-redirect=true"; } } public String logout() { String result="/index?faces-redirect=true"; FacesContext context = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest(); try { request.logout(); } catch (ServletException e) { log.log(Level.SEVERE, "Failed to logout user!", e); result = "/loginError?faces-redirect=true"; } return result; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
On Servlet 3.0 container, HttpServletRequest has login method that will validate the provided username and password in the password validation realm used by the web container login mechanism configured for the ServletContext.
Thanks for the example! This helped me out in my own code.
ReplyDeleteI used this to replace my previous solution, that was a JSP form with action="j_security_check".
ReplyDeleteIt works fine but I have a question:
In the old solution when clicking on a link in the restricted area the application continued with this page immediately after a successful login automatically.
I tried to simulate this with a outcome of the login method built from request, but had no success, since I found only data from the login page, not the former URI.
Any idea?
You can use javax.servlet.forward.request_uri request attribute to fetch original URI.
ReplyDeleteSomething like this:
request.getAttribute("javax.servlet.forward.request_uri")
I didn't try it but I think it should work.
Thanks Gordan, did not help, getAttribute returns null and when I look at all 9 attributes in debugger nothing suitable is there.
ReplyDeleteHm, I think that when you click on restricted area link, you are forwarded to login page and at that point you should try to fetch javax.servlet.forward.request_uri attribute(when the login page is displayed) and put it in hidden field or session. And then after you submit login form you can fetch that URI attribute from session or request parameter if you used hidden field.
ReplyDeleteGordan, Thanks on this excellent article. I've been trying the example w/o success. The only deviation from the example is I'm using JavaDB, not MySQL. Everything else is a copy from the article. I'm using GF3.1.1-build 12. Result.login always throws an exception and GF log shows only this:
ReplyDeleteWARNING: PWC4011: Unable to set request character encoding to UTF-8 from context /LoginApp, because request parameters have already been read, or ServletRequest.getReader() has already been called
SEVERE: jdbcrealm.invaliduserreason
WARNING: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
WARNING: Exception
Any suggestions on how to debug this. I know I made some mistake somewhere but, after several days, I can't find it.
Thanks, Luiz
Hi Gordan,
ReplyDeletethanks to your hint I realized following solution to the forwarding problem:
Moved the bean from @RequestScoped to @ViewScoped.
In a @PostConstruct Method I set
requestURI = (String) request.getAttribute("javax.servlet.forward.path_info");
which I use directly as outcome from login method.
This works and I could not recognize a problem till now.
Note: The UTF8 warning from Luiz Comment above I also have in logs.
Dieter
Here is solution to mentioned encoding warning:
ReplyDeletehttp://mpashworth.wordpress.com/2011/06/01/glassfish-3-1-pwc4011-warning-with-jsf-applications/
Just replace former sun-web.xml with glassfish-web.xml and add parameter-encoding tag.
The content of the xml file is the same, just the opening tag is glassfish-web-app instead of sun-web-app.
That will fix the problem.
Regarding that JavaDB problem - did you set correct database driver vendor for java db ?
I am having an issue finding my css when using the servlet 3 container solution. If I enter the correct username and password, subsequent pages in the same directories find the css file. I am using facelets/template. If I display the web page directly in a browser (no programming) it finds the css. If I use Firefox/Firebug it appears it can not find the css but shows my entire web page in the net/css tab. Do you know of anything that might be interferring with me finding the css other than newbie programming. Thanks for the great articles.
ReplyDeleteI am the anonymous from above. I tried removing the security info from my web.xml and using my login.xhtml (which I used as my 'form-login-page'as my welcome page and it found and used the specified css file. As a debugging measure I tried inserting request.contextPath in my template footer and with or without the security info it shows "/MyProject"
ReplyDeleteHi!If you are using JSF2 put css file in resources folder in the top-level web application root. For example resources/css/mycssfile.css. In facelet template you include that css file with
ReplyDelete<h:outputStylesheet name="mycssfile.css" library="css"/>
That should work.
Thanks for the quick response; however that is where my css file is. If I leave alone the login-config section in my web.xml but comment out the security-constraint section, it displays the login.xhtml correctly (with css style)
ReplyDeleteIt's strange behavior you described. You can send me your web.xml to email and I'll take a look.
ReplyDeleteHi!I checked your web.xml. I think the problem is that you have put your constraint on the root of application context:
ReplyDelete<url-pattern>/*</url-pattern>
Browser can not fetch css files until you are logged in. If you check in firefox firebug you'll see that he is trying to fetch css from:
http://localhost:8080/contextname/javax.faces.resource/fileName.css
Try to put secure pages in subfolder and set url pattern in web.xml in constraint tag like:
<url-pattern>/securepages/*</url-pattern>
Let me know if it works:)
Gordon, it worked!!!! Thanks so much. I still not sure why "/securepages/*" works and "/*" does not. Is "/*" not a valid url pattern even tho it starts with a "/" and ends with a "*"? Why can it find something off the context root when it can't find the context root? I will investigate this further. THANKS AGAIN for all your help and getting me back on track.
ReplyDeleteIf you have http://localhost:8080/contextname and pattern /* that means that everything after contextname in url is secured, and your css files are in http://localhost:8080/contextname/javax.faces.resource/ and can not be accessed. So, if you set pattern to /securepages/* that means that only urls that starts with http://localhost:8080/contextname/securepages/ are secured and http://localhost:8080/contextname/javax.faces.resource urls are accessible for everyone.
ReplyDeleteDuh. Sometimes the obvious escapes me. Maybe I need to be put in a "secure place". LOL. Thanks again.
ReplyDeleteThanks for a wonderful tutorial. However the problem I'm having is no matter what..even if the credentials are correct it keep sending me back to loginerror page. I checked the connections, config files still no luck. Below is the error I'm having (the drivers are correctly selected)
ReplyDeleteWARNING: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
WARNING: Exception
com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
at com.sun.enterprise.security.auth.login.LoginContextDriver.doPasswordLogin(LoginContextDriver.java:394)
at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:240)
at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:153)
at com.sun.web.security.RealmAdapter.authenticate(RealmAdapter.java:512)
at com.sun.web.security.RealmAdapter.authenticate(RealmAdapter.java:453)
at org.apache.catalina.connector.Request.login(Request.java:1932)
at org.apache.catalina.connector.Request.login(Request.java:1895)
at org.apache.catalina.connector.RequestFacade.login(RequestFacade.java:1146)
at gordan.showcase.AuthBackingBean.login(AuthBackingBean.java:34)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.sun.el.parser.AstValue.invoke(AstValue.java:234)
at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:297)
at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:88)
at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
at javax.faces.component.UICommand.broadcast(UICommand.java:315)
at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:794)
at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1259)
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1539)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:98)
at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:91)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:162)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:330)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:174)
at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:828)
at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:725)
at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1019)
Are you using glassfish 3.1? I had similar problem and solved it, here is explanation at the end of the article:
ReplyDeletehttp://jugojava.blogspot.com/2011/02/jdbc-security-realm-with-glassfish-and.html
Yes using glassfish 3.1.
ReplyDeleteThanks, Gordan. It helped.
Keep up the great work!
Gordan,
ReplyDeleteDo you have a similar tutorial for spring instead of JSF. If not can you guide to some good source (similar to the awesome work done here).
Thanks, You saved my day
ReplyDeleteI had code similar to yours working fine. I made lots of changes to other code in my project but I thought I kept this code the same; however now when I submit the form, log messages from the setUsername and setPassword indicate that the member variables are being set; yet when I go into my login method, and print log messages showing the values of username and password, the both say null. I would have thought this was a single call so that scope would not play a part. I have a beans.xml and my method is Serializable'. What am I missing here?
ReplyDeleteSorry. Imported wrong "@SessionScoped" from JSF rather than CDI. Please disregard above comment.
Deletethank for tutorial however I have a question about request.login(username, password);
ReplyDeletewhat does this code mean? when we call request.login what is jvm doing ? can you explain it?
Hi! request.login(username, password) will validate provided username and password with authentication realm you have configured in your application container. In my example I have jdbc realm in glassfish app server configured so this will validate my data in database.
ReplyDeleteHi Gordan, thanks for the tutorial. I have a problem with my application when trying to follow it, it does not recognize the login method of the HttpServletRequest. what could be missing?
ReplyDeleteresolved dependencies problem
DeleteHello Author Good, Your tutorial is very good I applied the same technique on my website and get the good result see here: https://www.ssccoachingchandigarh.in"
ReplyDeleteAwesome post. You Post is very informative. Thanks for Sharing.
ReplyDeletebest overseas consultants in bangalore
ms in mechanical engineering in germany
Masters in Mechanical Engineering in Germany
free education in germany
mba in germany
ms in germany for indian students
study ms in germany
study in germany consultants
Very interesting blog. Many blogs I see these days do not really provide anything that attracts others, but believe me the way you interact is literally awesome. You can also check my articles as well.
ReplyDeleteAdmissiongyan, the best german overseas education consultant in Bangalore, since 2012 for study abroad incl. Germany, France, Italy, Sweden, Ireland, NL, or UK.
best overseas consultants in bangalore
ms in mechanical engineering in germany
Masters in Mechanical Engineering in Germany
free education in germany
mba in germany
ms in germany for indian students
study ms in germany
study in germany consultants
yurtdışı kargo
ReplyDeleteresimli magnet
instagram takipçi satın al
yurtdışı kargo
sms onay
dijital kartvizit
dijital kartvizit
https://nobetci-eczane.org/
6LQ
شركة تسليك مجاري بالدمام 4TYNpmsolu
ReplyDelete