Reputation: 3083
We have a JSF App that uses a custom HttpAuthenticationMechanism for authentication, it forwards users to a login page if they do not have the role required and uses @AutoApplySession
for authenticated users.
We have recently added a CDI interceptor @RolesPermitted
that checks for required roles at Class or Method level in CDI beans (basically same at the @RolesAllowed
annotation for EJB) and goes to an error page if the roles are not found.
However I found that if a backing bean has eg. @RolesPermitted({'admin'})
at the Class level and a page in /admin/secured-page.xhtml
is requested as an unauthenticated user, the RolesPermitted
interceptor fires BEFORE the HttpAuthenticationMechanism so we get a security exception rather than being forwarded to the login page.
The backing bean class is being referenced in the Facelets page so it seems that the JSF view is being processed before the HttpAuthenticationMechanism
kicks in which seems wrong.
So the question is, how do I ensure the HttpAuthenticationMechanism is fired before my own security interceptor and user forwarded onto login page?
Here is the interceptor code:
@Interceptor
@RolesPermitted
@Priority(Interceptor.Priority.APPLICATION)
public class RoleInterceptor implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@Inject private SecurityContext sec;
@Inject private Logger log;
@AroundInvoke
public Object checkRole(InvocationContext ctx) throws Exception {
if(ctx.getMethod().getDeclaringClass().isAnnotationPresent(RolesPermitted.class)) {
String[] classRoles = ctx
.getMethod().getDeclaringClass()
.getAnnotation(RolesPermitted.class)
.value();
boolean hasRole = false;
log.debug("Checking if user has {} roles to execute {}",Arrays.asList(classRoles).toString(),ctx.getMethod().getDeclaringClass().getName());
for(String r:classRoles) {
if(sec.isCallerInRole(r)) {
hasRole=true;
}
}
if(!hasRole) {
throw new java.security.GeneralSecurityException("User does not have any of the required roles "+Arrays.asList(classRoles).toString());
}
}
if(ctx.getMethod().isAnnotationPresent(RolesPermitted.class)) {
String[] methodRoles = ctx
.getMethod()
.getAnnotation(RolesPermitted.class)
.value();
if(methodRoles.length>0) {
log.debug("Checking if user has {} roles to execute {}",Arrays.asList(methodRoles).toString(),ctx.getMethod().getName());
for(String r:methodRoles) {
if(sec.isCallerInRole(r)) {
return ctx.proceed();
}
}
throw new java.security.GeneralSecurityException("User does not have any of the required roles "+Arrays.asList(methodRoles).toString());
}
}
return ctx.proceed();
}
}
And backing bean that uses it...
@Named
@ViewScoped
@RolesPermitted({"admin"})
@Transactional(Transactional.TxType.REQUIRED)
public class AdminActions implements Serializable {
//some page logic
}
And our Authentication bean (bits removed for brevity)
@RequestScoped
@AutoApplySession
public class CustomAuthentication implements Serializable, HttpAuthenticationMechanism {
/**
*
*/
private static final long serialVersionUID = 1L;
@Inject private PasswordEncryptorEntities passwordEncryptor;
@Inject private Logger log;
/**
* Note: this method is called for all requests (including public) to determine if authentication is required
*/
@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
HttpMessageContext httpMessageContext) throws AuthenticationException {
log.trace("Validating request {}",request.getRequestURI());
if(httpMessageContext.isAuthenticationRequest()) {
Set<String> roles = new HashSet<String>();
if(loginPrincipal.coreRole().equals(Role.admin)) {
Admin admin = (Admin) loginPrincipal;
if(admin.isSuperUser()) {
roles.add(Role.adminSuperuser.getRole());
}
}
UserPrincipal up = new UserPrincipal(loginPrincipal);
httpMessageContext.getClientSubject().getPrincipals().add(up);
httpMessageContext.setRegisterSession(up.getName(), roles);
log.debug("Login successful for {} with roles {}",loginPrincipal.getFullname(),roles.toString());
return httpMessageContext.notifyContainerAboutLogin(up,roles);
}else if(httpMessageContext.isProtected() && request.getUserPrincipal()==null) {
//Protected resource requested with no logged in user, forward to login page
return httpMessageContext.forward(loginUrl);
log.error("Protected resource requested does not have corresponding Role: {}",request.getRequestURI());
}else if(httpMessageContext.isProtected() && request.getUserPrincipal()!=null) {
//Protected resource requested with wrong role set, forward to login page
UserPrincipal up = (UserPrincipal) request.getUserPrincipal();
return httpMessageContext.forward(up.getAppUser().coreRole().getLoginPage().getPath());
}
return httpMessageContext.doNothing();
}
}
In web.xml we declare the security constraints and roles, eg...
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin User Login Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
Upvotes: 1
Views: 233