spring-securityauthorizationspring-annotationsspring-elpre-authentication

Need to Pass the 'value' to my @CustomPreAuthorize annotation and use it in @PreAuthorize("hasAuthority(@myservice.check(#value))")


Currently I am writing my own custom @PreAuthorize annotation. My case is as follows,

  1. I am running my authorization KeyCloak server that holds the user details, roles and permission
  2. After Validation, I have stored the permission details in GrantedAuthority as follows "{rsname}:GET", "{rsname}:POST" ...

KeyCloak JWT permission structure:

"authorization": {
    "permissions": [
      {
        "scopes": [
          "GET",
          "DELETE",
          "POST"
        ],
        "rsid": "6ae9895f-3766-464f-82c4-44f598ec2a93",
        "rsname": "record"
      }
    ]
  }
  1. while using @PreAuthorize annotation in controller instead of hardcoding the resource name and scopes, we have to generalize it by getting the details from application.property we have achieved it as follows,

application.property:

auth:
  data:
    name1: record
    name2: device 

Property Detail Component class:

@ConfigurationProperties(prefix = "auth")
@Component
public class SecurityProperty {
    private Map<String, String> data;
    ....
}

Controller:

@RequestMapping (method = RequestMethod.GET,value = "/api/records",
        produces = {"application/json"})
@PreAuthorize ("hasAuthority (@securityProperty.getData(). get('name1') "
    + "+ ': GET')")
ResponseEntity<List<SomeDTO>> getRecords() {
        ...Some Logic
}


@RequestMapping(method = RequestMethod.GET,value = "/api/devices",
        produces = { "application/json" })
@PreAuthorize("hasAuthority(@securityProperty.getResources().get('name2') "
    + "+ ':GET')")
ResponseEntity<List<SomeDTO>> getDevices() {
        ...Some Logic
}
  1. So far this is working fine. Since we are creating big project we don't want to write this lengthy @PreAuthorize(XXXX) annotation so decided to create custom annotation that uses the @PreAuthorize. We have created @CustomPreAuthorize as below
@Retention(RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@PreAuthorize("hasAuthority(@securityProperty.getResources().get(#resource)"
        + ".concat(':GET'))")
public @interface CustomPreAuthorize {
    String resource();
}

And used this in controller

@RequestMapping (method = RequestMethod.GET,value = "/api/devices",
        produces = {"application/json"})
@CustomPreAuthorize (resource = "name2")
ResponseEntity<List<SomeDTO>> getDevices() {
        ...Some Logic
}

Issue:

  1. When I used like this when the API is called I am getting the following error
Failed to evaluate expression 'hasAuthority(@securityProperty.getResources().get(#resource).concat(':GET'))"
  1. So far what I understood is like the resource and scope are not getting recognized in the @PreAuthorize annotation level. Is it possible to read the values like this or is there any alternatives available?

Solution

  • Since there is no reply yet for the required fix, for now we have fixed this by adding the Aspect for the annotation and proceed with manual authority check using the SecurityContextHolder.

    @Before("@annotation(CustomPreAuthorize)")
    void annotationPointcut() {
        //business logic
    }
    

    We will check for the other solutions actively and post if we achieve it in more better way.