J. Lengel
J. Lengel

Reputation: 588

Quarkus Annotation-Based Interceptor with Non-Optional Arguments

This seems to be a hot topic based on the amount of questions asked but I have not found the answer I am looking for just yet. I want to implement a simple authorization service in my Quarkus app, but I seem to be repeating code over and over again.

Basically, I take in the JWT from the Authorization Http header and check if the role supplied in it is sufficient to access my endpoint:

public void someApiCall(@Context HttpHeaders headers) {
    authService.validate(ApiToken.SOME_API_CALL, headers); // Throws an exception when unauthorized
    
    //…
}

Now, I think this looks really clunky and I do not like the additional parameter that I need for every single Http endpoint. I have done some research into AOP and know how to add an interceptor which could validate the Http headers through an annotation which would be applied to my method:

@Authorize
public void someApiCall(/*…*/) { /*…*/ }

The issue is, I do not know how to pass in arguments into this annotation to specify the required role. I want something like this:

@Authorize(UserRole.SYSADMIN)

This seems pretty simple but I cannot figure it out. Below you will find the interceptor and annotation classes (Missing the required role of course):

Authorize.java

@Retention(value=RUNTIME)
@Target(value=METHOD)
public @interface Authorize {}

AuthorizeInterceptor.java

@Interceptor
@Priority(3000)
@Authorize
public class AuthorizeInterceptor {

    @Inject
    AuthorizationService authService;

    @AroundInvoke
    public void validateRole(InvokationContext ctx) {
        authService.validate(ApiToken.ALL, ((RestEndpoint)ctx.getTarget()).getHttpHeaders());
    }
}

RestEndpoint.java

public class RestEndpoint {

    @Context
    HttpHeaders headers;

    public HttpHeaders getHttpHeaders() { return headers; }
}

SomeResource.java

public class SomeResource extends RestEndpoint {

    @GET
    @Authorize
    public Object someApiCall() {
        /* do code directly */
    }
}

So, in conclusion, where I write @Authorize, I want to have @Authorize(UserRole.SOME_ROLE).

Thanks in advance!

Upvotes: 3

Views: 5436

Answers (1)

J. Lengel
J. Lengel

Reputation: 588

So, I managed to figure it out. It turns out that it isn't that hard, I just didn't know where to look.

Here are the modified classes:

Authorize.java

@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Authorize {
    // Nonbinding is very important. It makes the interceptor fire regardless of the value
    @Nonbinding ApiToken value();
}

AuthorizeInterceptor.java

@Interceptor
@Priority(3000)
@Authorize(ApiToken.NULL)
public class AuthorizeInterceptor {
    /* fields */

    public Object validate(InvokationContext ctx) throws Exception {
        authService.validate(/* stays the same */);
        return ctx.proceed();
    }
}

SomeResource.java

public class SomeResource {
    @GET
    @Authorize(ApiToken.SOME_API_CALL)
    public Object someApiCall() { /* implementation */ }
}

As Turing85 pointed out, a similar API already exists in JavaEE which implements the authorization functionality in the same way.

Upvotes: 4

Related Questions