使用@PreAuthorize时,Spring Security返回404而不是403。

6

在搜索了几天(查找类似问题,进行尝试和错误处理)后,我很想放弃……

问题是:我有一个基于Spring Boot的REST服务,使用Spring Security和JWT进行身份验证。现在我想通过 @PreAuthorize 注释来安全地调用一些方法,只允许经过授权的人员调用。

这似乎部分起作用,因为调用方法时,Spring返回404而不是我所期望的403。

我已经阅读了这个SO问题,并尝试了那里给出的答案,但情况没有改善。如建议的那样,我将@EnableGlobalMethodSecurity(prePostEnabled = true)注释从我的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);
}

我相信我错过了某个非常简单的东西,但我就是想不明白。

顺便说一下:如果请求许可证的用户具有管理员角色,则一切都按预期工作,因此问题并不是真正的404错误。


Spring Security 的确切响应是什么? - NatFar
响应体中没有任何内容,只有头部的404状态码。在进行更多调试时,我发现一开始是403错误。但在过滤器链的更高位置,它被改成了404错误。 - Frank
3个回答

5

您需要在安全配置中定义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);
    }
}

3
你可以将 Throwable.class 改成 AccessDeniedException.class,这样就不需要使用 instanceof - Michał Krzywański

0

可以通过注释@EnableGlobalMethodSecurity(prePostEnabled=true)启用全局方法安全性。结合使用@Preauthorize将为您的控制器创建一个新代理,它将失去请求映射,从而导致404异常。

为了处理这个问题,您可以使用注释@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true),该注释位于您的SecurityConfiguration类中。

还提供了另一个答案中的详细信息。


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