DaveB
DaveB

Reputation: 3083

CDI Interceptor fires BEFORE HttpAuthenticationMechanism validates request

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

Answers (0)

Related Questions