11

I have a JAX-RS service where I want all my users to access my services, but just those who have rights to see the result. Roles based security and existing REALMS and atuhentication methods doesn't fit my requirement.

For example:

  1. user authenticates against one REST service and I send him JWT token with his ID
  2. user asks for other resource and sends his JWT with his ID in each request
  3. I check his user id (from JWT) and if the business logic returns result I send them back, else I send empty result set or specific HTTP status

Question is: Where should I check for users ID, in some separate filter, security context or in every REST method implementation? How to provide REST methods with this ID, can securityContext be injected in every method after filtering request by ID?

I'm using GlassFish 4.1 and Jersey JAX-RS implementation.

D00de
  • 880
  • 3
  • 7
  • 26

2 Answers2

29

You can perform this logic in a ContainerRequestFilter. It pretty common to handle custom security features in here.

Some things to consider

  1. The class should be annotated with @Priority(Priorities.AUTHENTICATION) so it is performed before other filters, if any.

  2. You should make use of the SecurityContext, inside the filter. What I do is implement a SecurityContext. You can really implement it anyway you want.

Here's a simple example without any of the security logic

@Provider
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        SecurityContext originalContext = requestContext.getSecurityContext();
        Set<String> roles = new HashSet<>();
        roles.add("ADMIN");
        Authorizer authorizer = new Authorizer(roles, "admin", 
                                               originalContext.isSecure());
        requestContext.setSecurityContext(authorizer);
    }

    public static class Authorizer implements SecurityContext {

        Set<String> roles;
        String username;
        boolean isSecure;
        public Authorizer(Set<String> roles, final String username, 
                                             boolean isSecure) {
            this.roles = roles;
            this.username = username;
            this.isSecure = isSecure;
        }

        @Override
        public Principal getUserPrincipal() {
            return new User(username);
        }

        @Override
        public boolean isUserInRole(String role) {
            return roles.contains(role);
        }

        @Override
        public boolean isSecure() {
            return isSecure;
        }

        @Override
        public String getAuthenticationScheme() {
            return "Your Scheme";
        } 
    } 

    public static class User implements Principal {
        String name;

        public User(String name) {
            this.name = name;
        }

        @Override
        public String getName() { return name; }   
    }
}

A few things to notice

  • I've created a SecurityContext
  • I've added some roles, and used them for the isUserInRole method. This will be used for authorization.
  • I've created a custom User class, that implements java.security.Principal. I returned this custom object
  • Finally I set the new SecurityContext in the ContainerRequestContext

Now what? Let's look at a simple resource class

@Path("secure")
public class SecuredResource {
    @GET
    @RolesAllowed({"ADMIN"})
    public String getUsername(@Context SecurityContext securityContext) {
        User user = (User)securityContext.getUserPrincipal();
        return user.getName();
    }
}

A few things to notice:

  • SecurityContext is injected into the method.
  • We get the Principal and cast it to User. So really you can create any class that implements Principal, and use this object however you want.
  • The use of the @RolesAllowed annotation. With Jersey, there is a filter that checks the SecurityContext.isUserInRole by passing in each value in the @RolesAllowed annotation to see if the User is allowed to access the resource.

    To enable this feature with Jersey, we need to register the RolesAllowedDynamicFeature

    @ApplicationPath("/api")
    public class AppConfig extends ResourceConfig {
    
        public AppConfig() {
            packages("packages.to.scan");
            register(RolesAllowedDynamicFeature.class);
        }
    }
    
Nicola Isotta
  • 202
  • 3
  • 10
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • thanks for your reply. I thought I could make use of ContainerRequestFilter, but I don't have need for Roles and SecurityContext for my requirement. Is it too simple to use just servlet filter, and for each request to my REST resources i would do unmarshaling of previously sent JWT header, extract user info from it and pass it to service methods to determine user's grants levels inside each mehod? This seems like a simple pragmatic solution that just should work.. – D00de Apr 21 '15 at 11:00
  • Pretty much what you can do in a servlet filter, you can do in the ContainerRequestFilter. But with the latter, you have access to the Jersey application – Paul Samsotha Apr 21 '15 at 12:52
  • thanks, I've implemented your solution by Injecting JWT data to User principal and then fetched it from injected SecurityContext in every REST method I have. Have my up-vote and acceptance for your help fine sir. – D00de Apr 21 '15 at 17:38
  • How to do it in Wildfly 10? – max Mar 22 '16 at 11:06
  • How do you create "My Scheme", is that set of roles names? – Chris Sep 01 '16 at 07:55
  • Shouldn't it use AUTHORIZATION priority for checking the token rather than AUTHENTICATION? Just curious, since token checking is more of an AUTHORIZATION task, rather than authentication, which would be the token generation task. – nenchev Nov 06 '18 at 20:44
  • @nenchev No, authorization is to check _access rights_, while authentication is to check _who_ it is. So I think that the way I have it makes more sense as the token is to check who the person is, right? – Paul Samsotha Nov 06 '18 at 21:00
  • @PaulSamsotha I would definitely use AUTHORIZATION since you're not unpacking the token for the sake of just finding who the user is, you're doing that so that you can AUTHORIZE the incoming request. There's a reason why you named your other class "Authorizer". If you were checking who the user is and generating the JWT, that would definitely be the AUTHENTICATION process. Regardless, thanks for putting this up, I found it helpful. – nenchev Nov 08 '18 at 15:41
  • @nenchev so then what's the point of using the `@RolesAllowed` annotations? These are handled by jersey for you in an AUTHORIZATION filter. We unpack the token in the AUTHENTICATION filter and populate the SecurityContext with the user information. Jersey handles the authorization through our `@RolesAllowed` annotations. As far as the name the class, it's bad and misleading ;-) – Paul Samsotha Nov 08 '18 at 16:30
  • @nenchev ok I see the confusion. I didn't read the code. Yes you're right I did the authorization in my code above. But I would instead recommend using the jersey RolesAllowedDynamicFeature for the authorization where we can use the `@RolesAllowed` annotations. – Paul Samsotha Nov 08 '18 at 16:41
  • Yep, I'm definitely using @RolesAllowed, my concern was more around the fact that either way, parsing a JWT and extracting the roles is a authorization process step, rather than authentication. I was just wondering why it was setup that way, all clear though thanks. – nenchev Nov 08 '18 at 21:29
1

I was searching for an solution which is Jersey independent and works for Wildfly -> found this github example implementation:

https://github.com/sixturtle/examples/tree/master/jaxrs-jwt-filter

It should give you a hint how to solve it clean.

Implement a JWTRequestFilter which implements ContainerRequestFilter https://github.com/sixturtle/examples/blob/master/jaxrs-jwt-filter/src/main/java/com/sixturtle/jwt/JWTRequestFilter.java

as stated above and register the filter as resteasy provider in web.xml:

<context-param>
       <description>Custom JAX-RS Providers</description>
       <param-name>resteasy.providers</param-name>
       <param-value>com.sixturtle.jwt.JWTRequestFilter</param-value>
</context-param>
<context-param>
        <param-name>resteasy.role.based.security</param-name>
        <param-value>true</param-value>
</context-param>
Tunaki
  • 132,869
  • 46
  • 340
  • 423
max
  • 1,134
  • 10
  • 16