简体   繁体   中英

spring-boot custom annotation for validating headers

I am using spring-boot-1.5.10 and I am using spring-security in my application. I would like to create a custom annotation and which should using securityContext holder...Let me elaborate my question with sample code.

curl -X GET -H "roles: READ" -H "Content-Type: application/json" -H "Accept: application/json" -H "app-name: sample" -H "app-id: sample" -H "customer-id: 123" -H "market: EN" -H "country-code: EN" -H "Accept-Language: application/json" -H "Cache-Control: no-cache" " http://localhost:9992/api/v1/apps "

Controller

@GetMapping("/apps")
@PreAuthorize("hasAnyAuthority('ROLE_READ', 'ROLE_WRITE')")
public ResponseEntity<List<Apps>> getApps(@AuthenticationPrincipal AppAuthentication appAuthentication) {
    if(appAuthentication.isGrantedAnyOf("ROLE_READ") && isBlank(appAuthentication.getAppContext().customerId())) {
        throw new IllegalArgumentException("Missing header customerId");
    }
    if(appAuthentication.isGrantedAnyOf("ROLE_WRITE") && isBlank(appAuthentication.getAppContext().customerId()) && isBlank(appAuthentication.getAppContext().appId())) {
        throw new IllegalArgumentException("Missing header customerId & AppId");
    }
    //write business logic here
}

spring-security preAuthorize will only check if the roles are allowed.Also, I can enhance the preAuthorize annotation but it's common for many microservice and also i don't have permission to touch security realm. so, I would like to create a custom annotation. we should configure the roles & headers to validate for the particular roles. Like below

@GetMapping("/apps")
@PreAuthorize("hasAnyAuthority('ROLE_READ', 'ROLE_WRITE')")
@ValidateHeaders("role=ROLE_READ",value={"customerId","app-id"})
public ResponseEntity<List<Apps>> getApps(@AuthenticationPrincipal AppAuthentication appAuthentication) {
    //write business logic here
}

Any hint would be really appreciable.

Disclamer - I was working with spring boot 2, so not everything might be applicable for you

This is a stripped down version of something I've implemented a while back. I suggest implementing enums for roles and values.

You can't do redirects from @before anotations so you have to throw an exception and catch it with a global ex handler, redirect from there.

Consider also adding to the annotation optional fields - in case of multiple roles, match all or one, redirect path, exception type to invoke in case of no access. Then in the exception handler you can do redirects depending on which exception was invoked. Since you are doing redirects from gloabl ex handler, if you add a redirect path, you'll have to bundle it with the exception you are throwing, meaning you'll need custom exceptions.

Annotation

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateHeaders {

    Roles[] roles();
    String[] values();
}

Aspect Class

@Aspect
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) //Autowired annotated lombok generated constructor
public class ValidateHeadersAspect {


    private final @NonNull HttpServletRequest request; //Inject request to have header access
    private final @NonNull UserService userService;//Your user service here

    //Aspect can be placed on clas or method
    @Before("within(@com.org.package.ValidateHeaders *) || @annotation(com.org.package.ValidateHeaders)") 
    public void validateAspect(JoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        HasAccess validateHeaders = method.getAnnotation(ValidateHeaders.class);

        if(validateHeaders == null) { //If null it was a class level annotation
            Class annotatedClass = joinPoint.getSignature().getDeclaringType();
            validateHeaders = (ValidateHeaders)annotatedClass.getAnnotation(ValidateHeaders.class);
        }

        Roles[] roles = validateHeaders.roles(); //Roles listed in annotation
        String[] values = validateHeaders.values(); //Values listed in 


        //Validate request here ... determine isAuthorised


        if( !isAuthorized ){
            throw new HeaderAuthrizationException()
        }
    }

}

Exception Handler

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HeaderAuthrizationException.class)
    public RedirectView HeaderAuthrizationException(HeaderAuthrizationException ex) {
        return new RedirectView("/redirect");
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM