DZone
Java Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > JSF Application Level Access Control

JSF Application Level Access Control

Chris Watts user avatar by
Chris Watts
·
Sep. 02, 09 · Java Zone · Interview
Like (0)
Save
Tweet
19.56K Views

Join the DZone community and get the full member experience.

Join For Free

After trying to work out how to do generically, or at least easily configurable, I wrote a handy little tool for JSF which allows you to perform security control similar to how you can in the web.xml using security-constraint tags but allowing for application level authentication.

The trick is to use a PhaseListener, listening to the RESTORE_VIEW phase and checking the viewId in the afterPhase(). All requests go through this lifecycle, including the result of an action, allowing filtering to be performed on the viewId (URL).  For example a very simple check would be:

public class AccessControlPhaseListener implements PhaseListener
{
public void afterPhase(PhaseEvent event)
{
FacesContext context = event.getFacesContext();
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
SessionBean sessionBean = (SessionBean) session.getAttribute("sessionBean");
if (!sessionBean.isLoggedIn() && !"/login.xhtml".equals(context.getViewRoot().getViewId()))
context.getApplication().getNavigationHandler().handleNavigation(context, null, "login");
}
public PhaseId getPhaseId()
{
//ALL access go through RESTORE_VIEW and RENDER_VIEW (even direct url)
return PhaseId.RESTORE_VIEW;
}
}

To see my full code posting check out my blog post Access Control in JSF using a PhaseListener. I have written it to use configurable URL filters to set different required security levels.

package devgrok.jsf;

import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.ADMIN;
import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.LOGGED_IN;
import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.NONE;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.faces.util.MessageFactory;

import devgrok.jsf.SessionForm;
import devgrok.jsf.UrlFilter;

/**
* Phase Listener that checks the viewId (URL) against a set of filters to determine the required access level. If the
* correct level is not there then redirect.
*
* See {@link UrlFilter} for details on the url matching.
*
* @author Chris Watts 2009
*
*/
public class AccessControlPhaseListener implements PhaseListener
{
/** Logger for this class */
private static final Logger log = LoggerFactory.getLogger(AccessControlPhaseListener.class);

/** */
private static final long serialVersionUID = 1L;
private final static String SESSION_BEAN = "sessionBean";
private final HashMap<AccessLevel, List<UrlFilter>> levelFilters = new HashMap<AccessLevel, List<UrlFilter>>();

public enum AccessLevel
{
NONE, LOGGED_IN, USER_ACTIVE, ADMIN;
}

/**
*
*/
public AccessControlPhaseListener()
{
initLevels();

requires(LOGGED_IN)
.include("*")
.exclude("/index.xhtml")
.exclude("/login.xhtml")
.exclude("/user/newUser.xhtml");

requires(USER_ACTIVE)
.include("/user/*")
.exclude("/user/newUser.xhtml");

requires(ADMIN)
.include("/admin/*");
}

private void initLevels()
{
AccessLevel[] levels = AccessLevel.values();
for (int i = 1; i < levels.length; i++)
{
levelFilters.put(levels[i], new ArrayList<UrlFilter>());
}
}

private UrlFilter requires(AccessLevel level)
{
//ALL is default
if (level == NONE)
return null;

UrlFilter filter = new UrlFilter();
List<UrlFilter> list = levelFilters.get(level);
list.add(filter);
return filter;
}

/*
* (non-Javadoc)
*
* @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
public void afterPhase(PhaseEvent event)
{
try
{
//check have correct access
FacesContext context = event.getFacesContext();
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
SessionForm sessionBean = (SessionForm) session.getAttribute(SESSION_BEAN);
if (sessionBean == null)
{
log.error("Could not obtain instance of sessionBean");
return;
}

//can't use this here. only valid at render response phase?
String viewId = context.getViewRoot().getViewId();
AccessLevel required = requiredLevel(viewId);
log.debug("Required level={} for viewId={}", required, viewId);

//check if page require access:
switch (required) {
case NONE:
break;
case LOGGED_IN:
if (!sessionBean.isLoggedIn())
redirectLogin(event.getFacesContext(), sessionBean);
break;
case USER_ACTIVE:
if (!sessionBean.isActive())
redirectActive(event.getFacesContext());
break;
case ADMIN:
if (!sessionBean.isAdmin())
redirectAdmin(event.getFacesContext());
break;
default:
//error
log.error("huh?");
throw new IllegalArgumentException("Not a valid access level");
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
log.error("beforePhase caught exception", e);
}

}

/*
* (non-Javadoc)
*
* @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
public void beforePhase(PhaseEvent event)
{

}

private void redirectLogin(FacesContext context, SessionForm sessionForm)
{
//trigger login popup to be shown on render.
sessionForm.logIn();
addError(context, "access.loginrequired");
context.getApplication().getNavigationHandler().handleNavigation(context, null, "index");
}

private void redirectActive(FacesContext context)
{
addError(context, "access.activerequired");
context.getApplication().getNavigationHandler().handleNavigation(context, null, "userActivate");
}

private void redirectAdmin(FacesContext context)
{
addError(context, "access.adminrequired");
context.getApplication().getNavigationHandler().handleNavigation(context, null, "home");
}

/**
* Add keyed error/message.
*
* @param level
* @param key
* message key
*/
private void addError(FacesContext context, String key)
{
FacesMessage fMessage = MessageFactory.getMessage(key);
if (fMessage != null)
{
FacesContext facesContext = FacesContext.getCurrentInstance();
fMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage(null, fMessage);
}
}

/**
* Checks defined filters for view id, checks starting at the highest level down to NONE.
*
* @return the matching level or {@link AccessLevel#NONE} if none matching.
*/
private AccessLevel requiredLevel(String viewId)
{
AccessLevel[] levels = AccessLevel.values();
for (int i = levels.length - 1; i > 0; i--)
{
if (checkLevel(levels[i], viewId))
return levels[i];
}

return AccessLevel.NONE;
}

private boolean checkLevel(AccessLevel level, String viewId)
{
return matchUri(levelFilters.get(level), viewId);
}

private boolean matchUri(List<UrlFilter> list, String uri)
{
for (UrlFilter filter : list)
{
if (filter.matches(uri))
return true;
}
return false;
}

/*
* (non-Javadoc)
*
* @see javax.faces.event.PhaseListener#getPhaseId()
*/
public PhaseId getPhaseId()
{
//ALL access go through RESTORE_VIEW and RENDER_VIEW (even direct url)
return PhaseId.RESTORE_VIEW;
}

}

 

package devgrok.jsf;

import java.util.ArrayList;
import java.util.regex.Pattern;

/**
* An inclusion/exclusion filterset, similar to ant's fileset but does not support directories in the same style(**,
* etc).
*
* For example:
* <ul>
* <li>/servlet/* matches all urls starting with "/servlet/" e.g. /servlet/this.html
* <li>*.do matches all urls that end in ".do" - e.g. mypage.do
* <li>/servlet/*.do matches all urls starting with "/servlet/" and end in ".do" - e.g. /servlet/mypage.do
* </ul>
*
* @author Chris Watts 2009
*
*/
public class UrlFilter
{
private ArrayList<Pattern> include = new ArrayList<Pattern>();
private ArrayList<Pattern> exclude = new ArrayList<Pattern>();

public UrlFilter()
{

}

/**
* Include the wildcard(*) built pattern.
*
* @param pattern
* @return
*/
public UrlFilter include(String pattern)
{
include.add(generateExpression(pattern));
return this;
}

/**
* Exclude the wildcard(*) built pattern.
*
* @param pattern
* @return
*/
public UrlFilter exclude(String pattern)
{
exclude.add(generateExpression(pattern));
return this;
}

/**
* Checks to see if uri matches at least ONE inclusion filter and doesn't match ANY exclusion filters.
*
* @param uri
* @return
*/
public boolean matches(String uri)
{
boolean match = false;

//check inclusions
for (Pattern pattern : include)
{
match = match || pattern.matcher(uri).matches();
}

if (!match)
return false;

//check exclusions
for (Pattern pattern : exclude)
{
match = match && !pattern.matcher(uri).matches();
}
return match;
}

/** regular expression special character */
private static char[] specialChars = { '[', '\\', '^', '$', '.', '|', '?', '*', '+', '(', ')' };

/**
*
* @param input
* @return
*/
private static Pattern generateExpression(String input)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < input.length(); i++)
{
char letter = input.charAt(i);
if (letter == '*')
{
sb.append(".*");
}
else if (contains(specialChars, letter))
{
sb.append("\\" + letter);
}
else
{
sb.append(letter);
}
}
return Pattern.compile(sb.toString());
}

private static boolean contains(char[] array, char value)
{
if (array == null || array.length == 0)
{
return false;
}

for (int i = 0; i < array.length; i++)
{
char o = array[i];
if (o == value)
{
return true;
}
}

return false;
}
}
application

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • A Simple Guide to Heaps, Stacks, References, and Values in JavaScript
  • The Engineer’s Guide to Creating a Technical Debt Proposal
  • How to Configure Git in Eclipse IDE
  • Enough Already With ‘Event Streaming’

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo