我如何说服 spring 4.2 将 OPTIONS 请求传递给控制器

Posted

技术标签:

【中文标题】我如何说服 spring 4.2 将 OPTIONS 请求传递给控制器【英文标题】:how can i convince spring 4.2 to pass OPTIONS request through to the controller 【发布时间】:2016-06-29 04:19:24 【问题描述】:

我们在控制器上使用带有@RestController 注释的spring mvc,并且我们正在控制器中处理授权。我们使用相同的代码来设置响应 CORS 飞行前请求的允许方法。为此,我们有:

    <init-param>
        <param-name>dispatchOptionsRequest</param-name>
        <param-value>true</param-value>
    </init-param>

在dispatcher servlet的配置中,然后我们有:

    @RequestMapping(value="/some/collections", method=RequestMethod.OPTIONS)
    public void collectionOptions(
            HttpServletRequest req,
            HttpServletResponse res) 
        List<RequestMethod> methods = new ArrayList<>();
        // check actual permissions, add the appropriate methods
        CORS.setAllowedMethodHeaders(res,methods);
    

我们还有一个拦截器,可以在 CORS 飞行前对 CORS 进行基本检查,以查看来源是否可能拥有任何权限。

我们这样做主要是因为某些请求的权限实际上取决于@RequestParams,即:

    OPTIONS /api/collections?userId=122

如果您具有管理权限或者您实际上是 ID 为 122 的用户,则可能会被允许。此外,我们有 API 密钥,所以

    OPTIONS /api/collections?userId=122&apiKey=ABC

对于一个来源可能没问题,但对于另一个来源则不行。

这工作正常,但 Spring 4.2 现在决定是否处理 OPTIONS 请求,方法是调用:

    CorsUtils.isCorsRequest(request);

在AbstractHandlerMapping中然后返回

        HandlerInterceptor[] interceptors = chain.getInterceptors();
        chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);

而不是 HandlerMethod ...

我们需要的是某种方式告诉 spring 让控制器处理 OPTIONS 请求,无论存在什么预检请求处理程序。

我们似乎无法找到一个点,我们可以告诉内置的 CORS 处理是安静的,或者在某处配置一些子类以允许我们绕过新添加的代码:

  AbstractHandlerMapping.getHandler(HSR request)

这有可能吗?在我主动启用它之前(通过 WebMvcConfigurerAdapter 或通过那些 @CrossOrigin 注释),让这样的功能保持安静不是很好吗?

-------- 编辑 -------------

HTTP 标准对 OPTIONS 方法有如下说明:

OPTIONS 方法表示请求有关在由 Request-URI 标识的请求/响应链上可用的通信选项的信息。此方法允许客户端确定与资源相关的选项和/或要求,或服务器的功能,而无需暗示资源操作或启动资源检索。

只考虑 CORS,我认为拦截 CORS 选项调用虽然在控制器上映射了相应的方法不是正确的方法。是的,CORS 是您可以通过 OPTIONS 调用完成的一件事。但它绝不是唯一的。

if 没有任何映射,并且如果处理程序方法映射到不同的请求方法和 @CrossOrigin 注释,我希望触发内置 CORS 支持的假设会很好,但我不认为任何设置了原始标头的请求都应该自动只转到 CORS 处理程序。

【问题讨论】:

混合访问控制和 CORS 是个坏主意。例如,GET 请求没有预检请求,因此无论如何您都需要一个单独的安全层。 CORS 是关于浏览器,而不是服务器。 我并没有幻想 CORS 实际上阻止任何人调用东西。但是我见过的一些例子(Access-Control-Allow-Origin:*)对东西并不完全挑剔。我们有更多的信息可以用来做这个决定。而且我们显然也在实际通话中强制执行授权。我们使用 OPTIONS 调用的目的是允许 FE 根据用户的实际权限启用/禁用某些事情,而无需重复授权决策逻辑。 为什么不直接将OPTIONS 更改为GET,以允许mvc 处理程序照常进行映射? 【参考方案1】:

我刚刚说服 Spring 4.3 通过添加自定义处理程序映射将 CORS 预检传递给我的控制器:

public class CorsNoopHandlerMapping extends RequestMappingHandlerMapping 

    public CorsNoopHandlerMapping() 
        setOrder(0);  // Make it override the default handler mapping.
    

    @Override
    protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
             HandlerExecutionChain chain, CorsConfiguration config) 
        return chain;  // Return the same chain it uses for everything else.
    

注意:您仍然需要告诉 Spring 将 OPTIONS 请求发送到您的控制器开始 - 也就是说,在 dispatcherServlet 中将 dispatchOptionsRequest 设置为 true,就像它在 this question 中所说的那样。

为什么会这样

Sébastien 的above answer 建议使用您自己的CorsProcessor。据我所知,这仍然不会使用您的控制器作为处理程序;它只会将不同的 CorsProcessor 传递给它自己的 CORS 处理程序。

默认情况下,AbstractHandlerMapping#getCorsHandlerExecutionChain 方法在检测到预检时会丢弃您的控制器。它不使用您的控制器作为处理程序,而是实例化一个新的 PreFlightHandler 并使用它。见Spring source code。这是有问题的行:

chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);

它在这里所做的是用 PreFlightHandler 而不是您的控制器重建执行链。这不是我们想要的,所以我们可以重写它以返回输入链。

【讨论】:

【参考方案2】:

正如 zeroflagl 所建议的,我也认为混合访问控制和 CORS 并不是一个好主意。您应该记住,只有预检 CORS 请求是 OPTIONS 的。

如果您需要自定义 Spring CORS 处理,您可以使用 AbstractHandlerMapping#setCorsProcessor() 提供您自己的实现,该实现最终可以扩展 DefaultCorsProcessor

请注意,默认情况下,CORS 处理已启用,但不允许远程来源,因此很少需要自定义 CorsProcessor。有关this blog post 的更多信息。

您的访问控制检查可以作为常规的HandlerInterceptor 进行,因为成功的 CORS 预检请求之后将是一个实际的请求。

【讨论】:

对预检请求的响应实际上与访问控制无关。我们只是在那里使用相同的基本逻辑。我们用有效的方法来回答,以便让FE提前确定授权。问题是“我可以在这种情况下创建/删除/更新/读取这个对象吗”并且相同的答案用于 ACTUAL 调用(当调用实际控制器时)和预检请求(以便 FE 可以确定特定对象的权限,而无需实际尝试)。 @zeroflagL 我们遇到的问题是 OPTIONS 请求永远不会到达控制器(它已经必须知道授权问题的答案)。所以我们想要的只是一个直接通过的选项。 @rmalchow GET 请求(=读取)不受 CORS 约束。对于不是由浏览器发出的请求也是如此。 CORS 的想法是允许(而不是拒绝)在浏览器中运行的 javascript 应用程序发出本来会被禁止的请求。正如你所说,挑剔是没有意义的。老实说,我看不出你的方法的价值。如果您坚持,我会遵循@Sebestien 的回答。毕竟是权威答案。 +1 @zeroflag 我添加了更多上下文。我的观点是 CORS 不是 OPTIONS 调用的唯一应用程序。我想要的是有可能告诉请求映射器直接通过它,或者可能以某种方式让一个通用的 CORS 处理程序反过来通过。但目前的实现似乎不允许这样做。例如,将 实际上 映射到 DefaultCorsHandler 中的 HandlerMethod 会有所帮助,这样我就可以从自定义子类中调用它。 @rmalchow “CORS 不是 OPTIONS 调用的唯一应用程序” 没错,但您指的是专门的预检请求。您的客户不会故意发送OPTIONS 请求,是吗?将 CORS 与其他任何东西混合并不是一个好主意,这是您尝试做的事情。

以上是关于我如何说服 spring 4.2 将 OPTIONS 请求传递给控制器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring 4.2 中启用 CORS 过滤器

Spring Security 4.2 不工作

如何在 Spring Data JPA 查询中添加 OPTION(RECOMPILE)?

JavaEE与Spring

如何说服 nvcc 使用 128 位宽负载?

如何最好地说服人们升级 IE?