In questo post vediamo come realizzare un sistema minimo di login con sicurezza di base in una applicazione Java Server Faces 2.2.
Per prima cosa aprimo NetBeans 8, quindi creiamo una applicazione di tipo WebApplication
e specifichiamo come framework JSF 2.2
Per creare un progetto di tipo WebApplication
selezioniamo dal menu File
->New Project
, Poi Java Web
-> WebApplication
ed infine Next >
Chiameremo per semplicita la nostra applicazione JSFSecureLoginWebApplication
specifichiamo come sotto i vari passaggi
Impostiamo il nome
Le impostazioni dell’Application Server
Il framework JSF 2.2
L’url Pattern e il linguaggio utilizzato per le pagine JSF
Per i componenti non selezioniamo nulla
A questo punto abbiamo bisogno di creare due JavaBean
e un Filter
che servirà per gestire l’accesso alle pagine che definiremo sicure. Le pagine messe in sicurezza dal Filter
si troveranno nel percorso/directory /pages
. Chiameremo i due bean LoginBean
e NavigationBean
, la funzione di LoginBean
è quella di gestire l’accesso, quella di NavigationBean
la navigazione.
package org.caliman.jsfsecurewebapp.login;
import java.io.Serializable;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
@ManagedBean
@SessionScoped
public class LoginBean implements Serializable {
private static final long serialVersionUID = 1L;
protected final static Logger logger = Logger.getLogger(LoginBean.class.getName());
private String username;
private String password;
private boolean loggedIn;
@ManagedProperty(value = "#{navigationBean}")
private NavigationBean navigationBean;
public void validate(String username, String password) {
loggedIn = false;
if (username != null && password != null && username.trim().equals("admin")
&& password.trim().equals("secret!")) {
loggedIn = true;
}
//logged ok stuff...
if (this.loggedIn) {
//TODO
}
}
public String doLogin() {
validate(username, password);
if (loggedIn) {
return navigationBean.redirectToIndex();
}
FacesMessage msg = new FacesMessage("Login error: the user name or password for this app is incorrect!",
"The user name or password for this app is incorrect!");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext.getCurrentInstance().addMessage(null, msg);
return navigationBean.toLogin();
}
public String doLogout() {
loggedIn = false;
//logout stuff..
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return navigationBean.toLogin();
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isLoggedIn() {
return this.loggedIn;
}
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
}
public void setNavigationBean(NavigationBean navigationBean) {
this.navigationBean = navigationBean;
}
}
Il codice di NavigationBean
è riportato sotto
package org.caliman.jsfsecurewebapp.login;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class NavigationBean implements Serializable {
private static final long serialVersionUID = 2L;
public String redirectToLogin() {
return "/login.xhtml?faces-redirect=true";
}
public String toLogin() {
return "/login.xhtml";
}
public String redirectToIndex() {
return "/pages/index.xhtml?faces-redirect=true";
}
public String toIndex() {
return "/pages/index.xhtml";
}
}
Entrambi i bean sono annotati come @ManagedBean
e @SessionScoped
in quando devono avere scope di sessione ed essere gestibili nelle nostre pagine con le relative proprietà e metodi (che realizzano le azioni da associare ai pulsanti o link).
Un discorso a parte merita LoginFilter
, è un Filter
quindi è fondalmentalmente una Servlet
e come tale si comporterà permettendoci di verificare il percorso chiamato e discriminare tra le pagine o risorse accessibili in relazione al fatto che si sia accreditati o meno al sistema.
L’accreditamento è ovviamente legato alla correttezza dell’username
e password
imputati dall’utente. L’esempio è volutamente semplice, non v’è logica di controllo su un database o altra fonte persistente o remota riguardo all’username
e la password
. Ai fini didattici ci accontentiamo di verificare da codice se l’username
e la password
corrispondono ad una prefissata nel codice. E’ mia intenzione approfondire questo aspetto in uno dei prossimi post. L’idea mi permettarà di mostrare un semplice sistema di tabelle per gestire con JPA
la corrispondenza username/password e gestire un semplice sistema di ruoli.
Veniamo al codice della filter
package org.caliman.jsfsecurewebapp.login;
import java.io.IOException;
import javax.faces.application.ResourceHandler;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
LoginBean auth = (LoginBean) ((session != null) ? session.getAttribute("loginBean") : null);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = auth != null && auth.isLoggedIn();
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + "/" + ResourceHandler.RESOURCE_IDENTIFIER);
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) {
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response);
} else {
response.sendRedirect(loginURL);
}
}
@Override
public void init(FilterConfig config) throws ServletException {
// Nothing to do here!
}
@Override
public void destroy() {
// Nothing to do here!
}
}
A questo punto creiamo la nostra pagina JSF per la login e quella dell’indice della nostra applicazione a cui si potra accedere solo se accreditati
Dal menu di NetBeans creiamo una pagina per la login come sotto, la stessa operazione poi effettueremo per la creazione della pagina index.xhtml
che si troverà nel path messo in sicurezza dalla Filter.
Il codice per la login.xhtml
è riportato sotto, ed è ovviamente ridotto all’osso. Solo ciò che ci servirà, potremo poi utilizzare i template a modelli di portali per realizzare la nostra applicazione completa è ovviamente responsiva.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<meta charset="utf-8"/>
<title>JSFSecureLoginWebApplication:login</title>
</h:head>
<h:body>
<h:form id="loginForm">
<h1>Access</h1>
Username:<h:inputText value="#{loginBean.username}" required="true" id="username"/><br/>
Password:<h:inputSecret value="#{loginBean.password}" required="true" id="password"/><br/>
<h:commandButton value="Login" action="#{loginBean.doLogin}" id="loginButton"/><br/>
</h:form>
</h:body>
</html>
Ed infine quello per la pagina index.xhtml
.
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>JSFSecureLoginWebApplication:index</title>
</h:head>
<h:body>
<h1>Hello JSFSecureLoginWebApplication, username and password are correct.</h1>
<h:form>
<h:commandLink action="#{loginBean.doLogout}" value="">Logout</h:commandLink>
</h:form>
</h:body>
</html>
Il codice della nostra web.xml
è quello sotto
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>org.caliman.jsfsecurewebapp.login.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/pages/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>pages/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
Effettuaiamo il build della nostra applicazione web e poi lanciamola con run dal menu contestuale di progetto.