<security-constraint> <web-resource-collection> <web-resource-name>admin</web-resource-name> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint>The application user roles are defined in the web.xml file as security-role elements:
<security-role>
<role-name>admin</role-name>
</security-role>
The Servlet security model supports three different authentication method:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Admin Realm</realm-name>
</login-config>
To use the FORM method you also need to specify the path to the login page and
the login error page:
<login-config> <auth-method>FORM</auth-method> <realm-name>Secure Realm</realm-name> <form-login-config> <form-login-page>/login.htm</form-login-page> <form-error-page>/login.htm?auth-error=true</form-error-page> </form-login-config> </login-config>In your Click login.htm page you need to include a special j_security_check form which includes the input fields j_username and j_password. For example:
#if ($request.getParameter("auth-error")) <div style="margin-bottom:1em;margin-top:1em;color:red;"> Invalid User Name or Password, please try again.<br/> Please ensure Caps Lock is off. </div> #end <form method="POST" action="j_security_check" name="form"> <table border="0" style="margin-left:0.25em;"> <tr> <td><label>User Name</label><font color="red">*</font></td> <td><input type="text" name="j_username" maxlength="20" style="width:150px;"/></td> <td> </td> </tr> <tr> <td><label>User Password</label><font color="red">*</font></td> <td><input type="password" name="j_password" maxlength="20" style="width:150px;"/></td> <td><input type="image" src="$context/images/login.png" title="Click to Login"/></td> </tr> </table> </form> <script type="text/javascript"> document.form.j_username.focus(); </script>When using FORM based authentication do NOT put application logic in a Click Login Page class, as the role of this page is to simply render the login form. If you attempt to put navigation logic in your Login Page class, the JEE Container may simply ignore it or throw errors. Putting this all together below is a web.xml snippet which features security constraints for pages under the admin path and the user path. This configuration uses the FORM method for authentication, and will also redirect unauthorized (403) requests to the /not-authorized.htm page.
<web-app> .. <error-page> <error-code>403</error-code> <location>/not-authorized.htm</location> </error-page> <security-constraint> <web-resource-collection> <web-resource-name>admin</web-resource-name> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>user</web-resource-name> <url-pattern>/user/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <realm-name>Secure Zone</realm-name> <form-login-config> <form-login-page>/login.htm</form-login-page> <form-error-page>/login.htm?auth-error=true</form-error-page> </form-login-config> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app>
<click-app> <pages package="com.mycorp.dashboard.page" automapping="true"/> </click-app>To see how the page templates are mapped to Page classes set the application mode to debug and at startup the mappings will be listed out. An example Click startup listing is provided below:
[Click] [debug] automapped pages: [Click] [debug] /category-tree.htm -> com.mycorp.dashboard.page.CategoryTree [Click] [debug] /process-list.htm -> com.mycorp.dashboard.page.ProcessList [Click] [debug] /user-list.htm -> com.mycorp.dashboard.page.UserList
public class CustomerListPage extends Page { private ActionLink customerLink = new ActionLink("customerLink", this, "onCustomerClick"); .. public boolean onCustomerClick() { Integer id = customerLink.getValueInteger(); Customer customer = getCustomerService().getCustomer(id); CustomerDetailPage customerDetailPage = (CustomerDetailPage) getContext().createPage(CustomerDetailPage.class); customerDetailPage.setCustomer(customer); setForward(customerDetailPage); return false; } }To redirect to another page using the Page class you can obtain the pages path from the Context. In the example below we are passing through the customer id as a request parameter to the target page.
public class CustomerListPage extends Page { private ActionLink customerLink = new ActionLink("customerLink", this, "onCustomerClick"); .. public boolean onCustomerClick() { String id = customerLink.getValueInteger(); String path = getContext().getPagePath(CustomerDetailPage.class); setRedirect(path + "?id=" + id); return false; } }A quick way of redirecting to another page is to simply refer to the target class. The example below logs a user out, by invalidating their session, and then redirects them to the applications home page.
public boolean onLogoutClick() { getContext().getSession().invalidate(); setRedirect(HomePage.class); return false; }
#writeMenu($rootMenu)An advantage of using a macro to render you menu is that you can reuse the code across different applications, and to modify an applications menu you simply need to edit the WEB-INF/menu.xml file. A good place to define your macros is in the webroot /macro.vm file as it is automatically included by Click. Using macros you can create dynamic menu behaviour such as only rendering menus items a user is authorized to access with isUserInRoles().
#if ($menu.isUserInRoles()) .. #endYou can also use JavaScript to add dynamic behaviour such as drop down menus, for example see the Menu page in Click Examples.
public class BasePage extends Page { protected Logger logger; public Logger getLogger() { if (logger == null) { logger = Logger.getLogger(getClass()); } return logger; } }Using this pattern all your application bases should extend BasePage so they can use the getLogger() method.
public class CustomerListPage extends BasePage { public void onGet() { try { .. } catch (Exception e) { getLogger().error(e); } } }If you have some very heavy debug statement you should possibly use an isDebugEnabled switch so it is not invoked if debug is not required.
public class CustomerListPage extends BasePage { public void onGet() { if (getLogger().isDebugEnabled()) { String msg = .. getLogger().debug(msg); } .. } }Please note the Click logging facility is not designed for application use, and is for Click internal use only. When Click is running in production mode it will not produce any logging output.
<pages package="com.mycorp.page" automapping="true"/> <page path="click/error.htm" classname="ErrorPage"/> </pages>Generally application handle transactional errors using service layer code or via a servlet Filter and would not need to include error handling logic in an error page. Potential uses for a custom error page include custom logging. For example if an application requires unhandled errors to be logged to an application log (rather than System.out) then a custom ErrorPage could be configured. An example ErrorPage error logging page is provided below:
package com.mycorp.page.ErrorPage; .. public class ErrorPage extends net.sf.click.util.ErrorPage { public void onDestory() { Logger.getLogger(getClass()).error(getError()); } }