过滤servlet的一些方法

Filter some methods of servlet

我只需要将身份验证过滤器应用于 servlet 的某些方法。过滤器检查作为请求中的 header 键传递的 JWT 的有效性。

例如,session端点有一个POST(登录,public)和一个DELETE(注销,需要授权)方法。也许我可以将这两个拆分为注销和登录 servlet,但如果我有资源端点需要对 POST(创建)进行身份验证并且不需要对 GET 进行身份验证,则无法应用过滤器并创建两个为每个方法单独的 servlet 在大型应用程序中管理起来很痛苦。

是否可以通过不使用任何框架而仅使用 servlet 来解决此问题?

除此之外,如果我将过滤器应用于“/secure”路径,这对用户来说是不是很不友好? (访问 url 表示 "secure" 或 "public")。

在 servlet 和过滤器规范中,我不知道有什么方法可以根据请求类型实际进行映射 (GET/POST/PUT/DELETE...)。一种直接但具有侵入性的方法是传递描述应处理的请求类型的字符串。在这种情况下,如果请求不是这些类型,过滤器应立即调用过滤器链中的下一个元素。

如果您不想更改身份验证过滤器,您可以想象一个包装过滤器,它将实际过滤器的 class 作为参数以及传递给实际过滤器的参数。在这种情况下,环绕过滤器将:

  • 在初始化时,在初始化时创建真实过滤器的实例
  • 在过滤时测试请求是可接受的类型并且
    • 如果不直接调用FilterChain
    • 如果是,则使用当前请求、响应和链调用真正的过滤器

包装过滤器的可能代码可以是:

public class WrappingFilter implements Filter {
    Filter wrapped;
    List<String> methods;

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        // process FilterConfig parameters "actual_filter" and "methods"
        String classname = filterConfig.getInitParameter("actual_filter");
        String methodsString = filterConfig.getInitParameter("methods");
        methods = Arrays.asList(methodsString.split("|"));
        try {
            // create and initialize actual filter
            Class<?> clazz = Class.forName(classname);
            wrapped = (Filter) clazz.getConstructor().newInstance();
            wrapped.init(filterConfig);            
        } catch (ClassNotFoundException ex) {
            throw new ServletException(ex);
        } catch (NoSuchMethodException ex) {
            throw new ServletException(ex);
        } catch (SecurityException ex) {
            throw new ServletException(ex);
        } catch (InstantiationException ex) {
            throw new ServletException(ex);
        } catch (IllegalAccessException ex) {
            throw new ServletException(ex);
        } catch (IllegalArgumentException ex) {
            throw new ServletException(ex);
        } catch (InvocationTargetException ex) {
            throw new ServletException(ex);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String method = ((HttpServletRequest) request).getMethod();
        if (methods.contains(method)) {
            // appropriate method: call wrapped filter
            wrapped.doFilter(request, response, chain);
        }
        else {
            // directly pass request to next element in chain by-passing wrapped filter
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        // destroys wrapped filter
        wrapped.destroy();
    }    
}

通常应在 Web 应用程序中声明 WrappingFilter 过滤器。它应该直接通过 Java 初始化中的 FilterConfig 传递,或者通过 Web.xml 文件中的 filter_params 间接传递:

  • actual_filter: class 要被 WrappingFilter
  • 包装的实际过滤器的名称
  • methods:一个字符串,给出用 | 分隔的处理方法,例如 POST|DELETE
  • 包装过滤器的初始化将传递其他参数

注意:未经测试...

如果您只需要它进行身份验证,也许您不必编写 servlet 过滤器。在 web.xml 中或通过 @ServletSecurity 指定 SecurityConstraint 您可以将约束限制为某些 http 方法。参见例如here and there.

但是如果你真的必须写一个过滤器,我知道的唯一方法是在你的过滤器实现中使用 httpRequest.getMethod() 检查 http 方法,参见例如。this answer.

我最终创建了一个方法 addPublicFilter(),它将过滤器PublicFilter 应用于您作为参数传递的路径和方法。

public class PublicFilter implements Filter {

    private FilterConfig filterConfig;
    public static final String PUBLIC_METHODS = "com.example.access.PUBLIC_METHODS";
    static final String IS_PUBLIC = "com.example.access.IS_PUBLIC";

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final List<String> allowedMethods = Arrays.asList(filterConfig.getInitParameter(PUBLIC_METHODS).split(","));
        final String method = ((HttpServletRequest) request).getMethod();

        if (allowedMethods.contains(method)) request.setAttribute(IS_PUBLIC, true);
        chain.doFilter(request, response);
    }

    public void destroy() {}
}

然后在 Jetty 中我有方法:

private static void addPublicFilter(ServletContextHandler context, String path, String publicMethods) {
    final FilterHolder holder = new FilterHolder(PublicFilter.class);
    holder.setInitParameter(PublicFilter.PUBLIC_METHODS, publicMethods);
    context.addFilter(holder, path, EnumSet.of(DispatcherType.REQUEST));
}

我称之为:

addPublicFilter(context, "/*", "OPTIONS");
addPublicFilter(context, "/user/*", "POST");
addPublicFilter(context, "/session", "POST,GET");