【问题标题】:Spring Security returns 404 instead of 403 when using @PreAuthorize使用 @PreAuthorize 时 Spring Security 返回 404 而不是 403
【发布时间】:2019-09-18 05:21:50
【问题描述】:

在为此苦苦挣扎了几天(搜索类似问题,反复试验)后,我很想放弃......

所以问题是我有一个基于 Spring Boot 的 REST 服务,使用 Spring Security 和 JWT 进行身份验证。现在我想确保某些方法只能由使用@PreAuthorize-annotation 的授权人员调用。 这似乎有效,部分原因是 Spring 没有调用方法,而是返回 404。我本来期望 403。

我已阅读此SO-question 并尝试了那里给出的答案,但没有帮助。我已按照其他地方的建议将 @EnableGlobalMethodSecurity(prePostEnabled = true)-Annotation 从我的 SecurityConfiguration 移动到 Application 类,但它仍然不起作用。

我的安全配置如下所示:

@Configuration
@Profile("production")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Value("${adDomain}")
private String adDomain;

@Value("${adUrl}")
private String adUrl;

@Value("${rootDn}")
private String rootDn;

@Value("${searchFilter}")
private String searchFilter;

private final AuthenticationManagerBuilder auth;

private final SessionRepository sessionRepository;

@Autowired
public SecurityConfiguration(AuthenticationManagerBuilder auth, SessionRepository sessionRepository) {
    this.auth = auth;
    this.sessionRepository = sessionRepository;
}

@Override
public void configure(WebSecurity webSecurity) throws Exception
{
    webSecurity
            .ignoring()
            // All of Spring Security will ignore the requests
            .antMatchers("/static/**", "/api/web/logout")
            .antMatchers(HttpMethod.POST, "/api/web/login");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable() // Using JWT there is no need for CSRF-protection!
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository));
}

@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    ActiveDirectoryLdapAuthenticationProvider adProvider =
            new ActiveDirectoryLdapAuthenticationProvider(adDomain, adUrl, rootDn);
    adProvider.setConvertSubErrorCodesToExceptions(true);
    adProvider.setUseAuthenticationRequestCredentials(true);
    adProvider.setSearchFilter(searchFilter);
    adProvider.setUserDetailsContextMapper(new InetOrgPersonContextMapper());
    auth.authenticationProvider(adProvider);
    return super.authenticationManagerBean();
}

}

控制器方法如下所示

@RequestMapping(path = "/licenses", method = RequestMethod.GET)
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> getAllLicenses(@RequestParam("after") int pagenumber, @RequestParam("size") int pagesize
        , @RequestParam("searchText") String searchText) {       
    List<LicenseDTO> result = ...
    return new ResponseEntity<Object>(result, HttpStatus.OK);
}

我很确定我遗漏了一些非常简单的东西,但我就是不知道是什么。

顺便说一句:如果请求许可证的用户具有 ADMIN 角色,那么一切都按预期工作,所以问题不是真正的 404。

【问题讨论】:

  • 确切的 Spring Security 响应是什么?
  • 响应正文中没有任何内容。只是标题中的 404 状态。在进行更多调试时,我发现一开始有 403。它只是在过滤器链的上游更改为 404。

标签: java spring-boot spring-security


【解决方案1】:

需要在安全配置中定义exceptionHandling如下,

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable() // Using JWT there is no need for CSRF-protection!
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .exceptionHandling().accessDeniedHandler(new AccessDeniedExceptionHandler())
            .and()
            .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository));
}

可以如下定义AccessDeniedExceptionHandler类,

public class AccessDeniedExceptionHandler implements AccessDeniedHandler
{
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException ex) throws IOException, ServletException {
        response.setStatus(HttpStatus.FORBIDDEN);
    }
}

【讨论】:

    【解决方案2】:

    我终于找到了适合我目的的解决方案。我不知道这是否是解决这个问题的最佳方法,但只需添加一个 ExceptionHandler 就可以了。当没有适当的处理程序时,过滤器链深处的某个地方 403 突变为 404。 也许我要转储阅读和理解文档,但我没有找到任何建议你必须这样做的东西。所以也许我在解决这样的问题时是错误的,但这里是成功的代码(它是一个非常基本的实现,应该随着时间的推移而改进):

    @ControllerAdvice
    public class MyExceptionHandler {
    
        @ExceptionHandler(Throwable.class)
        @ResponseBody
        public ResponseEntity<String> handleControllerException(Throwable ex) {
            HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
            if(ex instanceof AccessDeniedException) {
                status = HttpStatus.FORBIDDEN;
            }
    
            return new ResponseEntity<>(ex.getMessage(), status);
        }
    }
    

    【讨论】:

    • 您可以将Throwable.class 更改为AccessDeniedException.class,而不必使用instanceof
    【解决方案3】:

    可以借助注解 @EnableGlobalMethodSecurity(prePostEnabled=true) 启用全局方法安全性。这个和@Preauthorize 的组合将为您的控制器创建一个新代理,它会丢失请求映射,这将导致 404 异常。

    要处理此问题,您可以使用 @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) 类中的注释 SecurityConfiguration

    another answer 中也提供了详细信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-12
      • 2018-12-05
      • 2018-08-15
      • 1970-01-01
      • 2020-10-14
      • 2014-07-30
      • 2015-10-23
      • 2018-08-28
      相关资源
      最近更新 更多