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

8
这是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。

我该如何解决这个问题?

4个回答

3

如果一个用户没有通过身份验证,为什么还要担心向他提供更多关于您系统的信息呢?我认同这种行为。就像如果一个用户没有权限���看您的硬盘,为什么还要让他发现您的硬盘目录树结构一样。

如果您真的想返回404状态码,您需要自定义 ExceptionTranslationFilter 中的 AuthenticationEntryPoint AccessDeniedHandler 。当用户没有足够的权限访问端点时(即AccessDeniedException发生) ,它们两个都会被调用。前者是针对匿名用户的,后者是针对已成功身份验证但权限不足的非匿名用户。

它们两个的默认实现(即 Http403ForbiddenEntryPoint AccessDeniedHandlerImpl )现在只会返回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是不寻常的。 - Avaldor
根据您的观点而定。在这种情况下,我更喜欢返回403,并附上我在答案中提到的原因。我认为这非常自然且有道理。 - Ken Chan

1

我认为你唯一的选择是以下内容:

@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();,除非你真的想明确说明。


0
我曾经遇到过类似的问题,无论是哪个URL都会返回403错误。 后来我发现,在Spring从1.5.x更新到更高版本时,声明contextPath的方式发生了变化。因此,由于我的application.yml文件中声明有误,Spring无法解析我的URL。
具体而言,它从以下形式改变了:
server:
  contextPath: /app-name

到:

server:  
  servlet:
    contextPath: /app-name

0
你可以在SecurityFilterChain中定义/error来实现这种行为。
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.authorizeHttpRequests(requests -> {
                    requests.requestMatchers("/public-end-points/**","/error").permitAll();
                    requests.anyRequest().authenticated();
                })
                .build();
    }
}

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