h1.
Using container managed, form-based security in a JSF web app.
A Practical Solution
h2. {color:#993300}*
But first, some background on the problem*{color}
The 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 to trigger authentication. 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. ([credit to Duncan Mills in this 2 years old article|http://groundside.com/blog/DuncanMills.php?title=j2ee_security_a_jsf_based_login_form&more=1&c=1&tb=1&pb=1]).
In this solution, I will use a pure JSF page as the login page that the end user interacts with. This page will simply gather the input for the username and password and pass that on to a plain old jsp proxy to do the actual submit. This will avoid the whole problem of having to use verbatim tags or a mixture of JSF and JSP in the user view.
h2. {color:#993300}*
Step 1: Configure the Security Realm in the Web App Container*{color}
What is a container? A
container is basically a security framework that is implemented directly by whatever app server you are running, in my case Glassfish v2ur2 that comes with Netbeans 6.1. Your container can have multiple
security realms. Each realm manages a definition of the security "*principles*" that are defined to interact with your application. A security principle is basically just a user of the system that is defined by three fields:
- Username
- Group
- Password
The security realm can be set up to authenticate using a simple file, or through JDBC, or LDAP, and more. In my case, I am using a "file" based realm. The users are statically defined directly through the app server interface. Here's how to do it (on Glassfish):
1. Start up your app server and log into the admin interface (http://localhost:4848)
2. Drill down into
Configuration >
Security >
Realms.
3. Here you will see the default realms defined on the server. Drill down into the
file realm.
4. There is no need to change any of the default settings. Click the
Manage Users button.
5. Create a new user by entering username/password.
Note: If you enter a group name then you will be able to define permissions based on group in your app, which is much more usefull in a real app.
I entered a group named "Users" since my app will only have one set of permissions and all users should be authenticated and treated the same.
That way I will be able to set permissions to resources for the "Users" group that will apply to all users that have this group assigned.
TIP: After you get everything working, you can hook it all up to JDBC instead of "file" so that you can manage your users in a database.
h2. {color:#993300}*
Step 2: Create the project*{color}
Since I'm a newbie to JSF, I am using Netbeans 6.1 so that I can play around with all of the fancy Visual Web JavaServer Faces components and the visual designer.
1. Start by creating a new Visual Web JSF project.
2. Next, create a new subfolder under your web root called "secure". This is the folder that we will define a
Security Constraint for in a later step, so that any user trying to access any page in this folder will be redirected to a login page to sign in, if they haven't already.
h2. {color:#993300}*
Step 3: Create the JSF and JSP files*{color}
In my very simple project I have 3 pages set up. Create the following files using the default templates in Netbeans 6.1:
1. login.jsp (A Visual Web JSF file)
2. loginproxy.jspx (A plain JSPX file)
3. secure/securepage.jsp (A Visual Web JSF file... Note that it is in the sub-folder named secure)
Code follows for each of the files:
h3. {color:#ff6600}*First we need to add a navigation rule to faces-config.xml:*{color}
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>loginproxy</from-outcome>
<to-view-id>/loginproxy.jspx</to-view-id>
</navigation-case>
</navigation-rule>
NOTE: This navigation rule simply forwards the request to loginproxy.jspx whenever the user clicks the submit button. The button1_action() method below returns the "loginproxy" case to make this happen.
h3. {color:#ff6600}*login.jsp -- A very simple Visual Web JSF file with two input fields and a button:*{color}
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:webuijsf="http://www.sun.com/webui/webuijsf">
<jsp:directive.page
contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"/>
<f:view>
<webuijsf:page
id="page1">
<webuijsf:html id="html1">
<webuijsf:head id="head1">
<webuijsf:link id="link1"
url="/resources/stylesheet.css"/>
</webuijsf:head>
<webuijsf:body id="body1" style="-rave-layout: grid">
<webuijsf:form id="form1">
<webuijsf:textField binding="#{login.username}"
id="username" style="position: absolute; left: 216px; top:
96px"/>
<webuijsf:passwordField binding="#{login.password}" id="password"
style="left: 216px; top: 144px; position: absolute"/>
<webuijsf:button actionExpression="#{login.button1_action}"
id="button1" style="position: absolute; left: 216px; top:
216px" text="GO"/>
</webuijsf:form>
</webuijsf:body>
</webuijsf:html>
</webuijsf:page>
</f:view>
</jsp:root>
h3. *login.java -- implent the
button1_action() method in the login.java backing bean*
public String button1_action() {
setValue("#{requestScope.username}",
(String)username.getValue());
setValue("#{requestScope.password}", (String)password.getValue());
return "loginproxy";
}
h3. {color:#ff6600}*loginproxy.jspx -- a login proxy that the user never sees. The
onload="document.forms[0].submit()" automatically submits the form as soon as it is rendered in the browser.*{color}
{code}
<?xml version="1.0" encoding="UTF-8"?>
<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"
pageEncoding="UTF-8"/>
<html>
<head> <meta
http-equiv="Content-Type" content="text/html;
charset=UTF-8"/>
<title>Logging in...</title>
</head>
<body
onload="document.forms[0].submit()">
<form
action="j_security_check" method="POST">
<input type="hidden" name="j_username"
value="${requestScope.username}" />
<input type="hidden" name="j_password"
value="${requestScope.password}" />
</form>
</body>
</html>
</jsp:root>
{code}
h3. {color:#ff6600}*secure/securepage.jsp -- A simple JSF{color}
target page, placed in the secure folder to test access*
{code}
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:webuijsf="http://www.sun.com/webui/webuijsf">
<jsp:directive.page
contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"/>
<f:view>
<webuijsf:page
id="page1">
<webuijsf:html id="html1">
<webuijsf:head id="head1">
<webuijsf:link id="link1"
url="/resources/stylesheet.css"/>
</webuijsf:head>
<webuijsf:body id="body1" style="-rave-layout: grid">
<webuijsf:form id="form1">
<webuijsf:staticText id="staticText1" style="position:
absolute; left: 168px; top: 144px" text="A Secure Page"/>
</webuijsf:form>
</webuijsf:body>
</webuijsf:html>
</webuijsf:page>
</f:view>
</jsp:root>
{code}
h2. {color:#993300}*_Step 4: Configure Declarative Security_*{color}
This type of security is called +declarative+ because it is not configured programatically. It is configured by declaring all of the relevant parameters in the configuration files: *web.xml* and *sun-web.xml*. Once you have it configured, the container (application server and java framework) already have the implementation to make everything work for you.
*web.xml will be used to define:*
- Type of security - We will be using "form based". The loginpage.jsp we created will be set as both the login and error page.
- Security Roles - The security role defined here will be mapped (in sun-web.xml) to users or groups.
- Security Constraints - A security constraint defines the resource(s) that is being secured, and which Roles are able to authenticate to them.
*sun-web.xml will be used to define:*
- This is where you map a Role to the Users or Groups that are allowed to use it.
+I know this is confusing the first time, but basically it works like this:+
*Security Constraint for a URL* -> mapped to -> *Role* -> mapped to -> *Users & Groups*
h3. {color:#ff6600}*web.xml -- here's the relevant section:*{color}
{code}
<security-constraint>
<display-name>SecurityConstraint</display-name>
<web-resource-collection>
<web-resource-name>SecurePages</web-resource-name>
<description/>
<url-pattern>/faces/secure/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>HEAD</http-method>
<http-method>PUT</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
<http-method>DELETE</http-method>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>User</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name/>
<form-login-config>
<form-login-page>/faces/login.jsp</form-login-page>
<form-error-page>/faces/login.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>User</role-name>
</security-role>
{code}
h3. {color:#ff6600}*sun-web.xml -- here's the relevant section:*{color}
{code}
<security-role-mapping>
<role-name>User</role-name>
<group-name>Users</group-name>
</security-role-mapping>
{code}
h3. {color:#ff6600}*Almost done!!!*{color}
h2. {color:#993300}*_Step 5: A couple of minor "Gotcha's"_ *{color}
h3. {color:#ff6600}*_Gotcha #1_*{color}
You need to configure the "welcome page" in web.xml to point to faces/secure/securepage.jsp ... Note that there is *_no_* leading / ... If you put a / in there it will barf all over itself .
h3. {color:#ff6600}*_Gotcha #2_*{color}
Note that we set the <form-login-page> in web.xml to /faces/login.jsp ... Note the leading / ... This time, you NEED the leading slash, or the server will gag.
*DONE!!!*
h2. {color:#993300}*_Here's how it works:_*{color}
1. The user requests the a page from your context (http://localhost/MyLogin/)
2. The servlet forwards the request to the welcome page: faces/secure/securepage.jsp
3. faces/secure/securepage.jsp has a security constraint defined, so the servlet checks to see if the user is authenticated for the session.
4. Of course the user is not authenticated since this is the first request, so the servlet forwards the request to the login page we configured in web.xml (/faces/login.jsp).
5. The user enters username and password and clicks a button to submit.
6. The button's action method stores away the username and password in the request scope.
7. The button returns "loginproxy" navigation case which tells the navigation handler to forward the request to loginproxy.jspx
8. loginproxy.jspx renders a blank page to the user which has hidden username and password fields.
9. The hidden username and password fields grab the username and password variables from the request scope.
10. The loginproxy page is automatically submitted with the magic action "j_security_check"
11. j_security_check notifies the container that authentication needs to be intercepted and handled.
12. The container authenticates the user credentials.
13. If the credentials fail, the container forwards the request to the login.jsp page.
14. If the credentials pass, the container forwards the request to *+the last protected resource that was attempted.+*
+Note the last point! I don't know how, but no matter how many times you fail authentication, the container remembers the last page that triggered authentication and once you finally succeed the container forwards your request there!!!!+
+The user is now at the secure welcome page.+
If you have read this far, I thank you for your time, and I seriously question your ability to ration your time pragmatically.
Kerry Randolph