How to make @PreAuthorize having higher precedence than @Valid or @Validated
A

3

19

I am using spring boot, and I have enabled the global method security in WebSecurityConfigurerAdapter by

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

And Below is my controller code

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

However, when a non-admin user try to do a PUT request, the JSR303 validator will kick in before @PreAuthorize. For example, non-admin user ended up getting something like "first name is required" instead of "access denied". But after user supplied the first name variable to pass the validator, access denied was returned.

Does anyone know how to enforce the @PreAuthorize get checked before @Valid or @Validated?

And I have to use this kind of method-level authorization instead of url-based authorization in order to perform some complex rule checking.

Artilleryman answered 8/3, 2015 at 5:18 Comment(3)
That will never happen. The @PreAuthorize is only invoked when the method is executed. However the @Valid is processed in preparing the execution of the method, which happens before the actual execution of the method. So this won't work. As a work around you could do manual validation instead of relying on the @Valid annotation.Flask
I see. Thanks for explaining how it works in background. I think it would be great if they can support validation after PreAuthorize in the future.Artilleryman
That would be difficult as that would change the whole way AOP works :). I can see an improvement for mapping urls and adding security to it. Currently only ant style expression and regular expressions are used, however I can see that one might want to use the path-variables for security also.Flask
C
1

For the same scenario, I have found reccomendations to implement security via spring filters.
Here is similar post : How to check security acess (@Secured or @PreAuthorize) before validation (@Valid) in my Controller?

Also, maybe a different approach - try using validation via registering a custom validator in an @InitBinder (thus skip the @valid annotation).

To access principal object in filter class:

  SecurityContextImpl sci = (SecurityContextImpl)     
session().getAttribute("SPRING_SECURITY_CONTEXT");

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

 }

In this case /{id} is a path param in the URL. To access path params in filter or interceptor class:

String[] requestMappingParams =    ((HandlerMethod)handler).getMethodAnnotation(RequestMapping.class).params()

        for (String value : requestMappingParams) {.
Cacogenics answered 8/3, 2015 at 7:49 Comment(5)
Thanks, I also saw that post, but spring filters (url-based) won't work in my case since i need to validate against the principal ID with url parameters. And I am using @ InitBinder in my implementation but @ PreAuthorize is still not called before the validator.Artilleryman
you can access the principal object in the servlet filter..does that help?Cacogenics
i added a snippet that accesses prinicipal object in your servlet filter class..i'm not sure if this makes it easier for u...Cacogenics
Thanks, I have never used servlet fitler before. So let's say if one of my REST PUT request is /user/{id} and I only allow user to edit their own account. How do I check against {id} in the filter? For example, user id =2 is only allowed to make a PUT request on /user/2 not /user/3?Artilleryman
Oh i see. Ok, i have added some code that shows how to access path param /{id} from request object. I think this woudl do the trick. Hope it helps!Cacogenics
E
3

I had the same issue and I found this post. The comment of M. Deinum helps me to understand what was going wrong

Here is what I did :

  1. The public method has the @PreAuthorize and do the check
  2. There is NO @Valid on the @RequestBody parameter
  3. I create a second method, private, where I do the DTO validation. Using the @Valid annotation
  4. The public methods delegates the call to the private one. The private method is called only is the public method is authorized

Example :

@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 ...
}
Ejecta answered 26/8, 2016 at 12:8 Comment(1)
is there any clean solution?Wendolyn
C
1

For the same scenario, I have found reccomendations to implement security via spring filters.
Here is similar post : How to check security acess (@Secured or @PreAuthorize) before validation (@Valid) in my Controller?

Also, maybe a different approach - try using validation via registering a custom validator in an @InitBinder (thus skip the @valid annotation).

To access principal object in filter class:

  SecurityContextImpl sci = (SecurityContextImpl)     
session().getAttribute("SPRING_SECURITY_CONTEXT");

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

 }

In this case /{id} is a path param in the URL. To access path params in filter or interceptor class:

String[] requestMappingParams =    ((HandlerMethod)handler).getMethodAnnotation(RequestMapping.class).params()

        for (String value : requestMappingParams) {.
Cacogenics answered 8/3, 2015 at 7:49 Comment(5)
Thanks, I also saw that post, but spring filters (url-based) won't work in my case since i need to validate against the principal ID with url parameters. And I am using @ InitBinder in my implementation but @ PreAuthorize is still not called before the validator.Artilleryman
you can access the principal object in the servlet filter..does that help?Cacogenics
i added a snippet that accesses prinicipal object in your servlet filter class..i'm not sure if this makes it easier for u...Cacogenics
Thanks, I have never used servlet fitler before. So let's say if one of my REST PUT request is /user/{id} and I only allow user to edit their own account. How do I check against {id} in the filter? For example, user id =2 is only allowed to make a PUT request on /user/2 not /user/3?Artilleryman
Oh i see. Ok, i have added some code that shows how to access path param /{id} from request object. I think this woudl do the trick. Hope it helps!Cacogenics
K
0

Use WebSecurityConfigurerAdapter.configure(HttpSecurity http) instead of @PreAuthorize

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
  @Override
  protected void configure(HttpSecurity http) throws    Exception {
    http
      .authorizeRequests()
      .mvcMatchers( "/path/**").hasRole("admin");
  }
}
Korfonta answered 15/7, 2019 at 4:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.