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

时间:2019-04-29 14:48:48

标签: java spring-boot spring-security

经过几天的努力(寻找类似问题,反复试验),我很想放弃...

所以问题是我有一个使用Spring Security和JWT进行身份验证的基于Spring Boot的REST服务。现在,我想确保某些方法只能由授权人员使用@PreAuthorize注释来调用。 这似乎部分有用,因为与其调用Spring返回404的方法,还不如调用403。

我已经阅读了SO-question,并尝试了此处给出的答案,但没有帮助。我已经按照其他地方的建议将@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);
}

我很确定我缺少一些非常简单的东西,但我只是无法弄清楚是什么。

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

3 个答案:

答案 0 :(得分:2)

您需要按如下所示在安全性配置中定义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);
    }
}

答案 1 :(得分:0)

我终于找到了适合自己目的的解决方案。我不知道这是否是解决此问题的最佳方法,但是仅添加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);
    }
}

答案 2 :(得分:0)

可以在注释@EnableGlobalMethodSecurity(prePostEnabled=true)的帮助下启用全局方法安全性。此和@Preauthorize的组合将为您的控制器创建一个新的代理,并将释放请求映射,这将导致404异常。

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

还在another answer中提供了详细信息。