quarkus: Overriding security context doesn't work anymore

Describe the bug We are facing an issue with the common security annotations @RolesAllowed, @DenyAll, @PermitAll on REST endpoints. These annotations seems not to work with the roles authorized with a specific security context. PS : I am anticipating the migration from quarkus 0.19.1 to 1.1.0 (I was waiting for the fix of this issue https://github.com/quarkusio/quarkus/issues/3516, that’s why we are always using an old version currently)

We are developing an application to provide some REST endpoints. These endpoints are reachable with a valid JWT token with the right roles. The REST resources are annotated with @RolesAllowed.

To handle the security on my REST endpoints, I have implemented a JWT filter :

@Provider
@ApplicationScoped
@JWTTokenNeeded
@Priority(Priorities.AUTHENTICATION)
public class JwtFilterWithCustomSecurityContext implements ContainerRequestFilter {
...

//This JWTFilter check if there is a valid JWT Token in http header Authorization, 
//if yes, I override the security context with a custom behaviour to retrieve the userPrincipal and to //check if  the specific role is well granted for a client : 
if (claimsSet != null && claimsSet.getSubject() != null) {
            final SecurityContext securityContext = requestContext.getSecurityContext();
            requestContext.setSecurityContext(new SecurityContext() {
                @Override
                public Principal getUserPrincipal() {
                    return () -> claimsSet.getSubject();
                }
                @Override
                public boolean isUserInRole(String role) {
                    Map<String, List<String>> mapUser = new HashMap<>();
                    mapUser.put("user1", List.of("role1", "role2"));
                    mapUser.put("user2", List.of("role3"));
                    List<String> userRight = mapUser.get(claimsSet.getSubject());
                    return userRight != null && userRight.contains(role);
                }
                @Override
                public boolean isSecure() {
                    return uriInfo.getAbsolutePath().toString().startsWith("https");
                }
                @Override
                public String getAuthenticationScheme() {
                    return "OIDC-JWT";
                }
            });
        }

} I have the same behaviour with the unit test.

Expected behavior If a valid JWT doesn’t have the roles then we can’t reach the REST Endpoint and get back an http code 403.

Actual behavior If the JWT is valid, the client can access to all REST endpoint, the annotation RolesAllowed doesn’t work, the client can access to all REST endpoint without restriction on the roles.

To Reproduce I have implemented a reproducer : the branch for the version working :
https://github.com/elamotte7/quarkus/tree/0.19.1-security-context

the branch with the quarkus upgrade that doesn’t work : https://github.com/elamotte7/quarkus/tree/1.1.0.CR1-security-context

Steps to reproduce the behavior: With unit test

  1. Launch test UserResourceTest in your IDE or mvn test

when the application is running

  1. Generate two tokens with TokenUtils.CreateToken(“user1”) and TokenUtils.CreateToken(“user2”) (user1 has “role1” and “role2”, user2 has “role3”)
  2. launch the application, mvn compile quarkus:dev
  3. open postman set the url to http://localhost:9000/secure/user set the Authorization to Bearer Token
  4. Launch requests with the two token already generated in the Authorization Token
  5. You can see that both user1 and user2 can access to the user resource “The user is granted to access to user resource”

Normally only user1 should be able to access to the user resource the user2 should retrieve an http code 403 with the message “Access forbidden: role not allowed”

Classes involved in the behaviour described above : com.elamotte.quarkus.poc.rest.secure.UserResource com.elamotte.quarkus.poc.rest.security.filter.JwtFilterWithCustomSecurityContext com.elamotte.quarkus.poc.rest.security.filter.MockJwtFilterWithCustomSecurityContext com.elamotte.quarkus.poc.it.rest.UserResourceTest

Configuration see repo github

Environment (please complete the following information):

  • Output of uname -a or ver: Darwin macbook-pro-de-colonel-d 19.0.0 Darwin Kernel Version 19.0.0: Thu Oct 17 16:17:15 PDT 2019; root:xnu-6153.41.3~29/RELEASE_X86_64 x86_64
  • Output of java -version: openjdk version “11.0.4” 2019-07-16 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.4+11) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.4+11, mixed mode)
  • Quarkus version or git rev: 1.1.0.CR1

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 29 (19 by maintainers)

Commits related to this issue

Most upvoted comments

I was looking into using the SecurityIdentityAugmentor and implementing a Supplier but I can’t figure out how I can access the request scope or to be exact how I can get access to the JWT token (header).

The AuthenticationRequestContex does not offer access to the request context like I expected.

The first step I mentioned about deprecating getRoles() and moving to hasRole() has gone in, so I am hoping to address the rest of this this week.

Hi,

Did you find a way to fix this ?

Thanks !

There’s something missing but that’s definitely not only documentation.

This should be wired by Quarkus.

@sberyozkin could you have a look at that one soon? Looks like something we will want fixed for 1.3.

Hi guys,

@wfrank2509 @elamotte7

I use this workaround (do not add quarkus security dependency)

@Provider
@Priority(Priorities.AUTHORIZATION)
public class JWTSecurityInterceptor implements ContainerRequestFilter {

    @Inject
    IJWTService jwtService;

    @Override
    public void filter(ContainerRequestContext context) {
        final ResourceMethodInvoker resourceMethodInvoker = (ResourceMethodInvoker) context.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
        final Method method = resourceMethodInvoker.getMethod();

        if (!method.isAnnotationPresent(PermitAll.class)) {
            if (method.isAnnotationPresent(DenyAll.class)) {
                context.abortWith(Response.status(Response.Status.FORBIDDEN).build());
                return;
            }

            final String authorization = context.getHeaderString("Authorization");
            if (authorization == null || authorization.isEmpty() || !authorization.startsWith("Bearer ")) {
                context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
                return;
            }

            final String token = authorization.substring("Bearer ".length());
            if (authorization == null || authorization.isEmpty()) {
                context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
                return;
            }

            final String username = jwtService.getUserNameFromToken(token);
            if (username == null) {
                context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
                return;
            }

            final Set<Role> roles = jwtService.getRoles(token);
            if (roles == null || roles.isEmpty()) {
                context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
                return;
            }

            if (method.isAnnotationPresent(RolesAllowed.class)) {
                final RolesAllowed rolesAllowedAnnotation = method.getAnnotation(RolesAllowed.class);
                final String[] rolesAllowed = rolesAllowedAnnotation.value();

                if (!Arrays.stream(rolesAllowed).filter(roleAllowed -> roles.contains(Role.valueOf(roleAllowed))).findFirst().isPresent()) {
                    context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
                    return;
                }
            }
        }
    }
}

Regards Janin Anthony

We probably need Stuart’s feedback on that one. Affecting it to him so that we can talk about it when he’s back.

Hi,

I had the same issue and I’ve been digging in debug mode into your repo. I found out that there’s a default RestEasy RoleBasedSecurityFilter but it’s not even registered by default. So you won’t manage to override it.

You can enable its registration by adding the following to your “appliccation.properties” file resteasy.role.based.security=true and voilà, tests are passing !

I saw the new quarkus release this morning, but this issue is not embedded in the 1.2.0.Final. Can you please give me some visibility on the resolution of this problem? It’s a very annoying point because with this flaw, my apis won’t be safe anymore. And we are stil using an old quakus version, 0.19.0 …