如何使@PreAuthorize比@Valid或@Validated具有更高的优先级

19

我正在使用Spring Boot,并且已经通过WebSecurityConfigurerAdapter启用了全局方法安全性。

@EnableGlobalMethodSecurity(prePostEnabled = true, order = Ordered.HIGHEST_PRECEDENCE) 

以下是我的控制器代码

@PreAuthorize("hasAnyRole('admin') or principal.id == id")
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public User updateUser(@PathVariable("id") String id,  @Valid @RequestBody   UserDto userDto) 
{ ....}

然而,当非管理员用户尝试进行PUT请求时,在@PreAuthorize之前JSR303验证器将会触发。 例如,非管理员用户最终会得到类似于“需要提供名字”的错误信息,而不是“拒绝访问”。但是在用户提供名字变量以通过验证器后,将返回“拒绝访问”。

有人知道如何强制使@PreAuthorize在@Valid或@Validated之前被检查吗?

我必须使用这种基于方法的授权方式来执行一些复杂的规则检查,而不能使用基于URL的授权方式。


2
那是不可能的。@PreAuthorize只有在方法执行时才会被调用。然而,@Valid在准备执行方法时就会被处理,这发生在实际执行方法之前。所以这样做行不通。作为一种解决方法,您可以手动验证,而不是依赖于@Valid注释。 - M. Deinum
我明白了。谢谢您解释它的后台工作原理。我认为如果他们能在未来支持PreAuthorize之后的验证,那将是很棒的。 - stevewho
那会很困难,因为那会改变AOP的整个工作方式 :). 我可以看到在映射URL和添加安全性方面有所改进。目前只使用ant样式表达式和正则表达式,但我可以看到有人可能也想要使用路径变量来实现安全性。 - M. Deinum
3个回答

3
我遇到了同样的问题,后来看到了这篇文章。M. Deinum的评论帮助我理解了出现了什么问题。
以下是我所做的:
1.公共方法使用@PreAuthorize并进行检查。 2.@RequestBody参数上没有@Valid注释。 3.我创建了第二个私有方法,在此方法中使用@Valid注释对DTO进行验证。 4.公共方法将调用委托给私有方法。只有在授权了公共方法时才会调用私有方法。
例如:
@RequestMapping(method = RequestMethod.POST)
@PreAuthorize("hasRole('MY_ROLE')")
public ResponseEntity createNewMessage(@RequestBody CreateMessageDTO createMessageDTO) {
    // The user is authorized
    return createNewMessageWithValidation(createMessageDTO);
}

private ResponseEntity createNewMessageWithValidation(@Valid CreateMessageDTO createMessageDTO) {
   // The DTO is valid
   return ...
}

1
有没有干净的解决方案? - Enrico Giurin

0
对于相同的情况,我已经找到了通过Spring过滤器实现安全性的建议。这里是类似的帖子: How to check security acess (@Secured or @PreAuthorize) before validation (@Valid) in my Controller? 另外,也许有一种不同的方法-尝试在@InitBinder中注册自定义验证器来使用验证(从而跳过@valid注释)。
要在过滤器类中访问主体对象:
  SecurityContextImpl sci = (SecurityContextImpl)     
session().getAttribute("SPRING_SECURITY_CONTEXT");

if (sci != null) {
    UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();

 }

在这种情况下,/{id}是URL中的路径参数。要在过滤器或拦截器类中访问路径参数:
String[] requestMappingParams =    ((HandlerMethod)handler).getMethodAnnotation(RequestMapping.class).params()

        for (String value : requestMappingParams) {.

1
谢谢,我也看到了那篇帖子,但是Spring的过滤器(基于URL)在我的情况下不起作用,因为我需要根据URL参数验证主要ID。而且我在我的实现中使用了@InitBinder,但是在验证器之前仍然没有调用@PreAuthorize。 - stevewho
你可以在Servlet过滤器中访问主要对象,这有帮助吗? - Paul John
我在你的Servlet过滤器类中添加了一个访问Principal对象的片段...我不确定这是否会让你更容易... - Paul John
谢谢,我以前从未使用过Servlet Filter。那么假设我的一个REST PUT请求是/user/{id},我只允许用户编辑自己的帐户。在过滤器中如何检查{id}?例如,用户ID = 2仅允许在/user/2上进行PUT请求,而不是在/user/3上进行PUT请求? - stevewho
哦,我明白了。好的,我添加了一些代码,展示了如何从请求对象中访问路径参数/ {id}。我认为这可以解决问题。希望能有所帮助! - Paul John

0
使用WebSecurityConfigurerAdapter.configure(HttpSecurity http)代替@PreAuthorize
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
  @Override
  protected void configure(HttpSecurity http) throws    Exception {
    http
      .authorizeRequests()
      .mvcMatchers( "/path/**").hasRole("admin");
  }
}

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