Chapter 8: Authentication"

From Documentation
Line 241: Line 241:
  
 
= Secure Your Pages =
 
= Secure Your Pages =
Although you have created a login page for authentication, but a user can bypass the login page and access the index page if he knows the page's URL. Therefore, we should protect zul pages from illegal access. ZK allows you to [[ZK Developer%27s Reference/UI Patterns/Page Initialization| initialize a zul page]] by implement a <javadoc>org.zkoss.zk.ui.util.Initiator</javadoc>. When we apply an <tt>Initiator</tt> to a zul, ZK will use it to perform initialization before creating components upon the zul.  
+
Although we have created a login page for authentication, but a user can bypass the login page and access the index page if he knows the page's URL. Therefore, we should protect zul pages from illegal access. ZK allows you to [[ZK Developer%27s Reference/UI Patterns/Page Initialization| initialize a zul page]] by implement a <javadoc>org.zkoss.zk.ui.util.Initiator</javadoc>. When we apply an initiator to a zul, ZK will use it to perform initialization before creating components upon the zul.  
  
We can create a initiator to check existence of user's credential in the session. If user's credential is absent, we determine it's a illegal request and we redirect it back to login page.
+
We can create a initiator to check existence of user's credential in the session. If a user's credential is absent, we determine it's a illegal request and redirect it back to login page.
 +
 
 +
 
 +
<source lang='java' high='1,6, 8, 10'>
 +
public class AuthenticationInit implements Initiator {
 +
 
 +
//services
 +
AuthenticationService authService = new AuthenticationServiceChapter8Impl();
 +
 +
public void doInit(Page page, Map<String, Object> args) throws Exception {
 +
 +
UserCredential cre = authService.getUserCredential();
 +
if(cre==null || cre.isAnonymous()){
 +
Executions.sendRedirect("/chapter8/login.zul");
 +
return;
 +
}
 +
}
 +
}
 +
</source>
 +
* Line 1, 6: A page initiator class should implement <javadoc>org.zkoss.zk.ui.util.Initiator</javadoc> and override <tt>doInit()</tt>.
 +
* Line 8: Get a user's credential from current session.
 +
* Line 10: Redirect users back to login page.
 +
 
 +
 
 +
Then we can apply this page initiator to those pages we want to protect from unauthenticated access.
 +
 
 +
'''chapter8\index.zul'''
 +
<source lang='xml' high='3'>
 +
<?link rel="stylesheet" type="text/css" href="/style.css"?>
 +
<!-- protect page by the authentication init  -->
 +
<?init class="org.zkoss.tutorial.chapter8.AuthenticationInit"?>
 +
<!-- authentication init have to locate before composition -->
 +
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/chapter8/layout/template.zul"?>
 +
 
 +
<zk>
 +
<include id="mainInclude" self="@define(content)" src="/chapter8/home.zul"/>
 +
</zk>
 +
</source>
 +
* Line 3: Because index.zul is the main page, we apply this page initiator on it.
 +
 
 +
After above steps, if you directly visit http://localhost:8080/tutorial/chapter8/index.zul without success authentication, you still see the login page.

Revision as of 01:58, 31 January 2013

Target Application

In this chapter, we will demonstrate how to implement authentication and protect your pages from illegal access. We will create a login page without sidebar as follows:

Tutorial-ch8-login.png


After login, we redirect users to index page with user name appeared at right side of the header.

Tutorial-ch8-index.png

Authentication

Authentication is the process to identify a user, and requesting an account and a password is a common way. Our login page also uses a template zul to keep a consistent style with index page. But it has no sidebar because users without login should not access main functions.

/chapter8/layout/template-anonymous.zul

<zk>
	<!-- free to access template, without sidebar  -->
	<borderlayout hflex="1" vflex="1">
		<north height="100px" border="none" >
			<include src="/chapter8/layout/banner.zul"/>
		</north>
		<center id="mainContent" autoscroll="true" border="none" self="@insert(content)">
			<!-- the main content will be insert to here -->
		</center>
		<south height="50px" border="none">
			<include src="/chapter3/footer.zul"/>
		</south>
	</borderlayout>
</zk>
  • Line 7: Define an anchor named content


Login form is built with Grid.

/chapter/8/login.zul

<?link rel="stylesheet" type="text/css" href="/style.css"?>
<!-- it is a login page, no authentication protection and use anonymous template -->
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/chapter8/layout/template-anonymous.zul"?>
<zk>
	<hbox self="@define(content)" vflex="1" hflex="1" align="center"
		pack="center" spacing="20px">
		<vlayout>
			<window id="loginWin"
				apply="org.zkoss.tutorial.chapter8.LoginController"
				title="Login with you name" border="normal" hflex="min">
				<vbox hflex="min" align="center">
					<grid hflex="min">
						<columns>
							<column hflex="min" align="right" />
							<column />
						</columns>
						<rows>
							<row>
								Account :
								<textbox id="account" width="200px" />
							</row>
							<row>
								Password :
								<textbox id="password" type="password"
									width="200px" />
							</row>
						</rows>
					</grid>
					<label id="message" sclass="warn" value="&#160;" />
					<button id="login" label="Login" />
					
				</vbox>
			</window>
			(use account='zkoss' and password='1234' to login)
		</vlayout>
	</hbox>
</zk>
  • Line 3: Apply a template zul with <?init ?>.
  • Line 5: Define a fragment to be inserted in the anchor content.
  • Line 24: Specify "password" at type, then user input will be masked.


Understand Session

Before proceeding to implement login function, we have to understand "session" first. A web application operates over HTTP protocol which is stateless; each request and its corresponding response is handled independently. Hence, a HTTP server cannot know whether a series of request sent from the same client or from different clients. That means the server cannot maintain client's state between multiple requests.

For HTTP protocol's characteristic, application servers maintain a session to keep client's state. When the server receives the first request from a client, the server creates a session and give the session a unique identifier. The client should send a request with the session identifier. The server can determine which session the request belongs to upon this session identifier.

In Java EE environment, an application server creates a javax.servlet.http.HttpSession object to track client's session. ZK's Session is a wrapper of HttpSession, you can use it to store user's data when you handle events. The usage:

  • Get current session: Sessions.getCurrent()
  • Store data into a session: Session.setAttribute("key", data)
  • Retrieve data from a session: Session.getAttribute("key")

Login

After understanding basic concept and usage, we can start to create a controller for login. This controller collects account and password input and validate them with a authentication service class. If the password is correct, the authentication service class saves user's credential into the session.

Controller used in chapter8/login.zul

public class LoginController extends SelectorComposer<Component> {
	
	//wire components
	@Wire
	Textbox account;
	@Wire
	Textbox password;
	@Wire
	Label message;
	
	//services
	AuthenticationService authService = new AuthenticationServiceChapter8Impl();

	
	@Listen("onClick=#login; onOK=#loginWin")
	public void doLogin(){
		String nm = account.getValue();
		String pd = password.getValue();
		
		if(!authService.login(nm,pd)){
			message.setValue("account or password are not correct.");
			return;
		}
		UserCredential cre= authService.getUserCredential();
		message.setValue("Welcome, "+cre.getName());
		message.setSclass("");
		
		Executions.sendRedirect("/chapter8/");
		
	}
}
  • Line 20: Authenticate a user with account and password and save user's credential into the session if it passes.
  • Line 28: Redirect to index page after successfully authenticated.


Authentication service class used in LoginController

public class AuthenticationServiceChapter8Impl extends AuthenticationServiceChapter5Impl{

	
	UserInfoService userInfoService = new UserInfoServiceChapter5Impl();
	
	@Override
	public boolean login(String nm, String pd) {
		User user = userInfoService.findUser(nm);
		//a simple plan text password verification
		if(user==null || !user.getPassword().equals(pd)){
			return false;
		}
		
		Session sess = Sessions.getCurrent();
		UserCredential cre = new UserCredential(user.getAccount(),user.getFullName());
		//just in case for this demo.
		if(cre.isAnonymous()){
			return false;
		}
		sess.setAttribute("userCredential",cre);
		

		return true;
	}
	...
}
  • Line 14: Get the current session.
  • Line 20: Store user credential into the session with a key userCredential which is used to retrieve credential back in the future.


After login, we want to display user's account in the banner. We can use EL to get user's account from UserCredential in the session.

<div hflex="1" vflex="1" sclass="banner">
	<hbox hflex="1" vflex="1" align="center">
		<!-- other components -->
		
		<hbox apply="org.zkoss.tutorial.chapter8.LogoutController" 
			hflex="1" vflex="1" pack="end" align="end" >
			<label value="${sessionScope.userCredential.name}" if="${not sessionScope.userCredential.anonymous}"/>
			<label id="logout" value="Logout" if="${not sessionScope.userCredential.anonymous}" sclass="logout"/>
		</hbox>
	</hbox>
</div>
  • Line 7: The sessionScope is an implicit object that you can use within EL to access session's attribute. It works as the same as getAttribute(). You can use it to get session's attribute with dot notation, e.g. ${sessionScope.userCredential} equals to calling getAttribute("userCredential") of a Session object.

Logout

When a user logout, we usually clear his data in the session and redirect him to login page. In our example, click "Logout" label in the banner can log you out.

<div hflex="1" vflex="1" sclass="banner" >
	<hbox hflex="1" vflex="1" align="center">
		<!-- other components -->
		
		<hbox apply="org.zkoss.tutorial.chapter8.LogoutController" 
			hflex="1" vflex="1" pack="end" align="end" >
			<label value="${sessionScope.userCredential.name}" if="${not sessionScope.userCredential.anonymous}"/>
			<label id="logout" value="Logout" if="${not sessionScope.userCredential.anonymous}" sclass="logout"/>
		</hbox>
	</hbox>
</div>
  • Line 8: We listen onClick event on logout label to perform logout action.

LogoutController.java

public class LogoutController extends SelectorComposer<Component> {
	
	//services
	AuthenticationService authService = new AuthenticationServiceChapter8Impl();
	
	@Listen("onClick=#logout")
	public void doLogout(){
		authService.logout();		
		Executions.sendRedirect("/chapter8/");
	}
}
  • Line 8: Call service class to perform logout.
  • Line 9: Redirect users to login page.


Logout performed by service class

public class AuthenticationServiceChapter8Impl extends AuthenticationServiceChapter5Impl{

	
	UserInfoService userInfoService = new UserInfoServiceChapter5Impl();
	
	...
	
	@Override
	public void logout() {
		Session sess = Sessions.getCurrent();
		sess.removeAttribute("userCredential");
	}
}
  • Line 11: Remove the stored user credential in the session.

Secure Your Pages

Although we have created a login page for authentication, but a user can bypass the login page and access the index page if he knows the page's URL. Therefore, we should protect zul pages from illegal access. ZK allows you to initialize a zul page by implement a Initiator. When we apply an initiator to a zul, ZK will use it to perform initialization before creating components upon the zul.

We can create a initiator to check existence of user's credential in the session. If a user's credential is absent, we determine it's a illegal request and redirect it back to login page.


public class AuthenticationInit implements Initiator {

	//services
	AuthenticationService authService = new AuthenticationServiceChapter8Impl();
	
	public void doInit(Page page, Map<String, Object> args) throws Exception {
		
		UserCredential cre = authService.getUserCredential();
		if(cre==null || cre.isAnonymous()){
			Executions.sendRedirect("/chapter8/login.zul");
			return;
		}
	}
}
  • Line 1, 6: A page initiator class should implement Initiator and override doInit().
  • Line 8: Get a user's credential from current session.
  • Line 10: Redirect users back to login page.


Then we can apply this page initiator to those pages we want to protect from unauthenticated access.

chapter8\index.zul

<?link rel="stylesheet" type="text/css" href="/style.css"?>
<!-- protect page by the authentication init  -->
<?init class="org.zkoss.tutorial.chapter8.AuthenticationInit"?>
<!-- authentication init have to locate before composition -->
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/chapter8/layout/template.zul"?>

<zk>
	<include id="mainInclude" self="@define(content)" src="/chapter8/home.zul"/>
</zk>
  • Line 3: Because index.zul is the main page, we apply this page initiator on it.

After above steps, if you directly visit http://localhost:8080/tutorial/chapter8/index.zul without success authentication, you still see the login page.