为什么拦截器不会在从 servlet 过滤器调用的 EJB 方法上触发?

why does interceptor not get fired on an EJB method called from servlet filter?

我想使用 servlet 过滤器在 URLs 上实现高级资源过滤,并使用拦截器在方法上实现低级操作过滤,但我的拦截器不会在从 servlet 过滤器调用的 EJB 方法上触发.

拦截器注释接口:

@Inherited
@InterceptorBinding
@Retention (RUNTIME)
@Target({TYPE, METHOD})
public @interface Permit {
    @Nonbinding
    String[] actions() default "N/A";
}

拦截器:

@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 过滤器:

@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
        }
    }
}

最后是我想用来管理 routing/interception 我所有 servlet 的 EJB RulesBean 的样子;

规则:

@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 ...
}

不幸的是 PermitInterceptor 在进入 canViewAllCustomers() 方法之前没有被调用。

然而,令人惊讶的是,当 canViewAllCustomers() 被创建 public 并直接作为 rules.canViewAllCustomers() 而不是通过辅助方法 rules.isAllowed(String url) 调用时,PermitInterceptor 被触发。但这对我的情况没有帮助,因为它没有为我的 URL checks 提供单一入口点,这实际上意味着我必须在 Servlet 过滤器中进行所有检查。

问题:请问任何人都可以更清楚地说明事情以这种方式发生的原因吗?......非常欢迎提出有关实现这种情况的最佳方法的建议.谢谢。


注意: (提供更多视角) 您可能想知道为什么我要这样做,或者更具体地说,为什么 RuleBean 甚至存在...原因很简单,因为我的很多 Servlet 没有做太多事情,除了对 [=47 的路由响应=]触发服务器端 DataTables ajax 调用的视图,该调用填充表 ,因此我真的需要确保对视图的请求不会' 甚至达到获取视图的 if...else 条件,除非满足拦截器中的权限检查。

请参阅下面的示例 servlet;

@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(...){
            ...
        }
    }
}

您直接在 isAllowed() 中调用 canViewAllCustomers(),这使应用程序服务器没有机会拦截该调用。

拦截与代理 classes 一起工作。当您将 EJB 注入您的 servlet 时,就像您所做的那样:

@EJB
private RulesBean rules;

实际注入的不是 EJB 实例,而是应用程序服务器在运行时创建的代理 class(您可以通过调试器看到这一点)。该代理 class 上的调用将被事务、自定义拦截器等拦截,然后委托给实际的 class 实例。

所以你需要做的是将 canViewAllCustomers() 放入一个新的 EJB,这样你就可以让应用程序服务器注入你的 RulesBean class, 或者您可以从 isAllowed() 中检索 RulesBean class 的引用,如下所示:

@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;
    }
}