Spring过滤器:获取servlet的URL模式

3

假设我已经定义了这些REST端点:

@RequestMapping(path = "/user")
@RestController
public class ConfigUserController {

    [...]
    
    @GetMapping(path = "/variables/")
    public ResponseEntity<List<Variable>> getAllVariables(...)
        [...]
    }
    
    @GetMapping(path = "/variables/{name}")
    public ResponseEntity<Variable> getVariableByName(...)
        [...]
    }
    
    @PutMapping(path = "/variables/{name}/{value}")
    public ResponseEntity<Variable> setVariableByName(...)
        [...]
    }
}

我定义了两个过滤器(日志和授权),在过滤器内部,我想获取与当前请求匹配的url-pattern。使用上面的示例:
- 如果请求是GET /variables,则要获取“/variables”。 - 如果请求是GET /variables/myfancyname,则需要“/variables/{name}”。 - 如果请求是PUT /variables/myfancyname/myvalue,则需要“/variables/{name}/{value}”。
基本上我需要在我的过滤器doFilter(ServletRequest request,ServletResponse response,FilterChain chain)方法中获取映射注释中定义的路径。
理想情况下,我希望可以像这样访问过滤器中的url-pattern值:
@Component
@Order(3)
public class MyAuthorizationRequestFilter extends GenericFilterBean {
    [...]
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // here I get the value
        String urlPattern = request.useSomeMethod().toGetUrlPattern();
        [...]
        boolean auth = false;
        // here I do stuff
        [...]
        // finally let's see if the user is authorized
        if (auth) {
            chain.doFilter(request, responde);
        } else {
            throw SomeAuthorizationError();
        }
    }
    [...]
}

我一直在寻找,但没有找到可行的解决方案。

如果可能的话,我不想扫描集合或web.xml文件(我没有),也不想使用反射,因为代码将在每次过滤器触发时执行,我不想影响性能。

欢迎提供链接或建议。

编辑:添加详细信息,添加过滤器示例代码

2个回答

5

1. 使用 Servlet 过滤器的解决方案(在 Filter doFilter 之后)## 这里不推荐该解决方案。

过滤器拦截请求在到达 DispatcherServlet 之前,因此它们非常适合处理粗粒度任务,例如:

  • 认证
  • 日志记录和审计
  • 图像和数据压缩
  • 任何我们想要与 Spring MVC 解耦的功能

注意* 在尝试调用 getAttribute 之前,先进行 doFilter

您的控制器

@RequestMapping(path = "/user")
@RestController
public class ConfigUserController {

    @GetMapping(path = "/variables/")
    public ResponseEntity<List<Variable>> getAllVariables() {
        return null;
    }

    @GetMapping(path = "/variables/{name}")
    public ResponseEntity<Variable> getVariableByName(@PathVariable("name") String name) {
        return new ResponseEntity<Variable>(new Variable(name), HttpStatus.OK);
    }

    @PutMapping(path = "/variables/{name}/{value}")
    public ResponseEntity<Variable> setVariableByName() {
        return null;
    }
}

自定义筛选器
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // TODO
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
        } finally {
            String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
            System.out.println("Request template is, " + pattern);
        }
    }

}

输出:
 Request template is, /user/variables/{name}

请注意:在尝试调用getAttribute之前,请先执行chain.doFilter。

2. 使用拦截器的解决方案

HandlerIntercepors用于在DispatcherServlet和我们的控制器之间拦截请求。这是在Spring MVC框架内完成的,提供对处理程序和ModelAndView对象的访问。

自定义拦截器

@Component
public class LoggerInterceptor implements HandlerInterceptor {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
            throws Exception {

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
            throws Exception {

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        String path = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        System.out.println("path : " + path);
        return true;
    }}

在InterceptorRegistry中注册它。
@Component
public class CustomServiceInterceptorAppConfig implements WebMvcConfigurer {
   @Autowired
   LoggerInterceptor loggerInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(loggerInterceptor);
   }
}

enter image description here

3. 使用Java反射API的解决方案。

单例作用域的Bean并设置为预实例化(默认情况下)是在容器创建时创建的。这是一个一次性的过程,您不必担心性能问题。

您可以预先扫描所有的@RequestMapping,@GetMapping, @PostMapping等注释,并保留它们,然后在过滤器中匹配模式以获取所需结果。

以下是示例:

Bean

@Configuration
public class Config {

    @Bean
    public List<String> getPatterns() throws ClassNotFoundException {

        List<String> str = new ArrayList<String>();
        Class clazz = Class.forName("com.example.demo.controller.ConfigUserController"); // or list of controllers,
                                                                                            // //you can iterate that
                                                                                            // list
        for (Method method : clazz.getMethods()) {
            for (Annotation annotation : method.getDeclaredAnnotations()) {
                if (annotation.toString().contains("GetMapping")) {
                    str.add(annotation.toString());
                    System.out.println("Annotation is..." + annotation.toString());
                }
                if (annotation.toString().contains("PutMapping")) {
                    str.add(annotation.toString());
                    System.out.println("Annotation is..." + annotation.toString());
                }
                if (annotation.toString().contains("PostMapping")) {
                    str.add(annotation.toString());
                    System.out.println("Annotation is..." + annotation.toString());
                }
            }
        }

        return str;

    }

自定义筛选器
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomFilter implements Filter {
    @Autowired
    Config con;
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // TODO
        try {
            //filter match the pattern to get the desired result
            System.out.println(con.getPatterns());
        } catch (ClassNotFoundException e) {
            
        }
        chain.doFilter(request, response);
    }

我尝试过类似的事情,但我不确定在认证或授权过滤器中使用chain.doFilter()是否可以,如果我省略该部分,则大多数属性为null。也许我错了,没关系吗? - moretti.fabio
如果可能的话,您能添加安全/日志记录过滤器吗? - Sibin Rasiya
我已经在我的问题中加入了一个过滤器示例。 - moretti.fabio
如果在调用doFilter之前尝试调用getAttribute值,将会得到null。 - Sibin Rasiya
同时,我使用Java反射API,并借助@Bean添加了一种解决方案。作为单例范围并设置为预实例化(默认值)的Bean将在容器创建时被创建。 - Sibin Rasiya
1
你的解决方案让我找到了正确的方向,最终我创建了一个自定义授权过滤器,其中包含一个 Set<RequestMappingInfo> patterns,该集合在 ContextRefreshedEvent 中被填充。因此,当我在过滤器中时,可以遍历 RequestMappingInfo 并使用 getMatchingCondition(HttpRequest) 获取将要使用的映射,然后使用 getPatternsCondition 访问模式。 - moretti.fabio

2

根据问题所需,更新答案以使用GenericFilterBean.doFilter。 您可以通过添加finally子句来完成此操作。这是必要的,因为在调用request.getAttribute之前,需要先执行chain.doFilter(request, response),因为直到请求生命周期后期才设置属性。因此,如果您尝试根据传入的URI模板执行某些操作,则可能不适合子类化GenericFilterBean,因为您需要先调用chain.doFilter。

 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            //Do something
            chain.doFilter(request, response);
        } finally {
         String pathTemplate = (String) 
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
         System.out.println("Incoming Request path Template from Generic Filter : " + pathTemplate);
}


}

备选方案:

假设您在此处使用Spring Web。 您可以定义一个HandlerInterceptor并将其注册到InterceptorRegistry中。

@Component
public class LogPathTemplateInterceptor
        implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        String pathTemplate = (String)request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        System.out.println("Incoming Request path Template : " + pathTemplate);
        return true;
    }
}

在定义完之后,将其注册到InterceptorRegistry中。请看拦截器添加的路径模式。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Autowired
    private LogPathTemplateInterceptor logInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor)
                .addPathPatterns("/variables/**");
    }
}

这应该记录请求模板路径,而不是带有变量的路径。


谢谢,那很有趣,但如果我想在FilterBean内部的doFilter(ServletRequest request,ServletResponse response,FilterChain chain)方法中访问模板路径怎么办? 有什么可能性吗? - moretti.fabio
@moretti.fabio 更新了我的答案。 - Binu George

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接