I want to implement high level resource filtering on URLs with a servlet filter and lower level action filtering on methods with an interceptor but my interceptor does not get fired on the EJB method called from the servlet filter.
Interceptor annotation Interface:
@Inherited
@InterceptorBinding
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface Permit {
@Nonbinding
String[] actions() default "N/A";
}
The Interceptor:
@Permit
@Interceptor
public class PermitInterceptor {
@AroundInvoke
public Object verifyPermission(InvocationContext context) throws Exception {
Method method = context.getMethod();
if(method.isAnnotationPresent(Permit.class)) {
Permit permitAnnotation = method.getAnnotation(Permit.class);
List<String> permittedActions = Arrays.asList(permitAnnotation.actions());
List<String> userActions = SecurityContextHandler.getActiveUser().getActions();
if(!Collections.disjoint(permittedActions, userActions)){
return context.proceed();
}
}
throw new PermissionException("You do NOT have the required permissions to perform this action");
}
}
Servlet Filter:
@WebFilter(urlPatterns = {"/*"})
public class AccessFilter implements Filter {
@EJB
private RulesBean rules;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getRequestURI();
if(rules.isAllowed(url)){
chain.doFilter(request, response);
}else{
//handle as necessary
}
}catch(Exception ex){
//handle as necessary
}
}
}
And finally here's what the EJB RulesBean
that I want to use to manage routing/interception for all my servlets looks like;
Rules:
@Stateless
@LocalBean
public class RulesBean {
private static final String CUSTOMERS = "/customers"
public boolean isAllowed(String url) throws PermissionException {
switch(url){
case CUSTOMERS: return canViewAllCustomers();
default: return true;
}
}
/*This should trigger PermitInterceptor before entering method and
should throw my custom PermissionException if permission fails*/
@Permit(actions={"ViewCustomers"})
private boolean canViewAllCustomers(){
return true;
}
...
//Other tests carried out here ...
}
Unfortunately PermitInterceptor
doesn't get called before entering canViewAllCustomers()
method.
Amazingly however, PermitInterceptor
gets triggered when canViewAllCustomers()
is made public and called directly as rules.canViewAllCustomers()
instead of through the helper method rules.isAllowed(String url)
. But this isn't helpful in my case, as it gives me no single entry point for my URL checks which essentially means I have to do all the checks in the Servlet Filter.
QUESTION: Please can anybody shed more light on the reason why things are occurring in this manner?... and suggestions about the best way to implement this scenario is highly welcome. Thanks.
RuleBean
even exists at all... The reason is simply because a good number of my Servlets aren't doing much except route response to a view that triggers a server-side DataTables ajax call which populates the tables, hence I really need to ensure that the request for the view doesn't even get through to the if...else
condition that fetches the view unless the permission checks in the interceptor is satisfied.
See sample servlet below;
@WebServlet ("/customers/*")
public class CustomerServlet extends VelocityViewServlet {
private static final String CUSTOMERS = "/customers"
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
if(uri.equals(CUSTOMERS)){
String view = Path.getTemplate("/customers/index.vm");
request.setAttribute("headerTitle", "Customers");
request.getRequestDispatcher(view).forward(request, response);
}else if(...){
...
}
}
}
You invoke canViewAllCustomers()
within isAllowed()
directly, which gives the Application Server no chance to intercept the call.
Interception works with proxy classes. When you inject the EJB into your servlet, like you did with:
@EJB
private RulesBean rules;
what actually gets injected is not an EJB instance, but a proxy class, that the application server created at runtime (you can see this with the debugger). Invocations on that proxy class will be intercepted for transactions, custom interceptors, etc. and then delegated to the actual class instance.
So what you need to do is either put canViewAllCustomers()
into a new EJB, that you can let the application server inject into your RulesBean
class,
or you can retrieve a reference of your RulesBean
class from inside isAllowed()
like so:
@Stateless
@LocalBean
public class RulesBean {
private static final String CUSTOMERS = "/customers"
@Resource
SessionContext ctx;
public boolean isAllowed(String url) throws PermissionException {
switch(url){
case CUSTOMERS: return self().canViewAllCustomers();
default: return true;
}
}
private RulesBean self() {
return ctx.getBusinessObject(RulesBean.class);
}
/*This should trigger PermitInterceptor before entering method and
should throw my custom PermissionException if permission fails*/
@Permit(actions={"ViewCustomers"})
public boolean canViewAllCustomers(){
return true;
}
}