调用不存在的端点时收到 403 而不是 404

Posted

技术标签:

【中文标题】调用不存在的端点时收到 403 而不是 404【英文标题】:Receiving 403 instead of 404 when calling non existing endpoint 【发布时间】:2021-12-31 09:21:41 【问题描述】:

这是 Spring Security 配置的典型部分:

@Override
protected void configure(HttpSecurity http) throws Exception 
    http.csrf().and().cors().disable();
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();
    http.authorizeRequests().anyRequest().authenticated();

http.authorizeRequests().anyRequest().authenticated() 有问题。

添加后,当我调用不存在的端点时,例如:GET: /api/v1/not-existing,我收到 403 而不是预期的404 响应。

我想保护我所有的资源,但我想在调用不存在的资源时得到 404。

我该如何解决?

【问题讨论】:

【参考方案1】:

我可以接受这种行为。如果用户未通过身份验证,为什么还要担心告诉他有关您系统的更多信息。就像一个用户没有权限查看你的硬盘,为什么要让他发现你的硬盘目录树结构。

如果真的要返回 404 ,需要在 ExceptionTranslationFilter 中自定义 AuthenticationEntryPointAccessDeniedHandler 。如果用户没有足够的权限访问端点(即AccessDeniedException发生),它们都会被调用。前者用于匿名用户,后者用于非匿名用户(即认证成功但没有足够权限的用户)

它们的两个默认实现(即Http403ForbiddenEntryPointAccessDeniedHandlerImpl)现在都简单地返回 403 。您必须自定义它们,以便它们首先检查是否存在现有端点来服务当前的HttpServletRequest,如果没有则返回 404。您可以通过循环遍历DispatcherServlet 中的HandlerMapping 并检查是否有任何HandlerMapping 可以处理当前的HttpServletRequest

首先创建一个执行此检查的对象:

public class HttpRequestEndpointChecker 

    private DispatcherServlet servlet;

    public HttpRequestEndpointChecker(DispatcherServlet servlet) 
        this.servlet = servlet;
    

    public boolean isEndpointExist(HttpServletRequest request) 

        for (HandlerMapping handlerMapping : servlet.getHandlerMappings()) 
            try 
                HandlerExecutionChain foundHandler = handlerMapping.getHandler(request);
                if (foundHandler != null) 
                    return true;
                
             catch (Exception e) 
                return false;
            
        
        return false;
    
   

然后自定义AuthenticationEntryPointAccessDeniedHandler使用这个对象进行检查:

public  class MyAccessDeniedHandler extends AccessDeniedHandlerImpl 
    private HttpRequestEndpointChecker endpointChecker;

    public MyAccessDeniedHandler(HttpRequestEndpointChecker endpointChecker) 
        this.endpointChecker = endpointChecker;
    

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException 

        if (!endpointChecker.isEndpointExist(request)) 
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
         else 
            super.handle(request, response, accessDeniedException);
        
    

public class MyAuthenticationEntryPoint extends Http403ForbiddenEntryPoint 

        private HttpRequestEndpointChecker endpointChecker;

        public MyAuthenticationEntryPoint(HttpRequestEndpointChecker endpointChecker) 
            this.endpointChecker = endpointChecker;
        

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException authException) throws IOException 
            if (!endpointChecker.isEndpointExist(request)) 
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
             else 
                super.commence(request, response, authException);
            
        

并配置它们:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private DispatcherServlet dispatcherServlet;

    @Autowired
    private HttpRequestEndpointChecker endpointChecker;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
            ..............
            ..............
            http.exceptionHandling()
                .authenticationEntryPoint(new MyAuthenticationEntryPoint(endpointChecker))
                .accessDeniedHandler(new MyAccessDeniedHandler(endpointChecker));

    

    @Bean
    public HttpRequestEndpointChecker endpointChecker() 
        return new HttpRequestEndpointChecker(dispatcherServlet);
    


【讨论】:

我一直认为默认行为是 404 而不是 403,不是吗?... :o 而且我返回 403 是不寻常的。 取决于你如何看待它。在这种情况下,出于我在答案中提到的原因,我更愿意返回 403。我觉得很自然也很有意义【参考方案2】:

在我看来,您唯一的选择如下:

@Override
protected void configure(HttpSecurity http) throws Exception 
    http.csrf().and().cors().disable();
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();
    http.authorizeRequests().antMatchers(all-your-endpoints).authenticated();
    http.authorizeRequests().anyRequest().permitAll();

您需要将all-your-endpoints 替换为匹配所有端点的正则表达式或多个正则表达式。事实上,您甚至可以摆脱 http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();,除非您真的想明确说明它。

【讨论】:

以上是关于调用不存在的端点时收到 403 而不是 404的主要内容,如果未能解决你的问题,请参考以下文章

当您决定返回 403 时,始终返回 404

Spring Boot POST 请求返回 http 状态 405“方法不允许”而不是 HTTP 状态 404

Spring Boot:发生 RESTFul 错误(404、403、500)时获取 JSON 正文

对 .htaccess 的请求应返回 404 而不是 403

有没有办法强制 apache 返回 404 而不是 403?

Amazon S3 - 尽管有 GetObject 限制,但返回 403 错误而不是 404