springspring-bootspring-security

@PreAuthorized annotation don't recognizing custom UserDetails


I'm trying to do method security in Spring Service by annotating it with @PreAuthorized annotation. I want to check if the id claim from the JWT token equals id field from my Request Body, I'm trying to do

@PreAuthorize("r.id == principal.getId()")
public ResponseEntity<?> update(UpdateRequest r) {
        return ResponseEntity.ok().build();
    }

But it seems like it's trying to use Spring built-in UserDetails without my custom logic. I'm sure that in my public class JwtAutentificationFilter extends OncePerRequestFilter using my custom UserDetails. I've tried Custom class principal in @PreAuthorize solution, where it was suggested to make custom Bean for that and I got an error

Failed to evaluate expression @decider.mayUpdate(principal,r.id)'

Where it was basically this

public static boolean mayUpdate(Object principal, Long id){
        CustomUserDetails user = (CustomUserDetails) principal;
        return user.getId().equals(id);
    }

I know that in every method I can get security context and do something like

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal();

if(!userDetails.getId().equals(r.getId())){
...throw an error
}

But I'm going to have a lot of methods, and I don't want to do it in such a straight way. So I want to know if there's a way to put my custom principal into @PreAuthorized or do this authorization in some easier way!


Solution

  • I found a way to fix this!

    If any of you need to use custom UserDetails in @PreAuthtorize, create a new bean where you put your Authorization logic using SecurityContext you can do it like this:

    You should annotate the method you want to secure like

    @PreAuthorize("@decider.tokenIdEqualsIdFromRequest(#r.id)")
        public ResponseEntity<?> update(UpdateRequest r) {
            return ResponseEntity.ok().build();
    }
    

    And you have to create the bean where your logic using security context will be located, for me it's like

    @Service
    public class Decider {
    
        public static boolean tokenIdEqualsIdFromRequest(Long id){
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    
            CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal();
    
            return userDetails.getId().equals(id);
        }
    }