Jax RS Authorization
Asked Answered
M

3

9

I have an existing code at a class which is extended from javax.ws.rs.core.Application

...
Context childContext = component.getContext().createChildContext();
JaxRsApplication application = new JaxRsApplication(childContext);
application.add(this);
application.setStatusService(new ErrorStatusService());
childContext.getAttributes().put("My Server", this);
...

ChallengeAuthenticator challengeGuard = new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "REST API Realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(null, "user"));
User owner = new User("admin", "admin");
realm.getUsers().add(owner);
realm.map(owner, Role.get(null, "admin"));
//Attach verifier to check authentication and enroler to determine roles
challengeGuard.setVerifier(realm.getVerifier());
challengeGuard.setEnroler(realm.getEnroler());
challengeGuard.setNext(application);
// Attach the application with HTTP basic authentication security
component.getDefaultHost().attach(challengeGuard);

I don't have a web.xml at my code. I would like to add authorization to my code. This: https://restlet.com/technical-resources/restlet-framework/guide/2.3/core/security/authorization does not apply to me since I don't have restlet resources.

How can I implement jax rs authorization into my code?

EDIT 1: Existing code uses restlet JAX-RS extension: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs

I've tried that at my jax-rs resource class:

@GET
@Path("/")
public String getStatus() {
  if (!securityContext.isUserInRole("admin")) {
    throw new WebApplicationException(Response.Status.FORBIDDEN);
  }
  ...
}

However, it throws 403 even I log in with admin user.

EDIT 2:

When I check here: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs There is a piece of code:

this.setRoleChecker(...); // if needed

This may solve my issue but I don't know how to set a role checker.

PS: I use jersey 1.9 and restlet 2.2.3.

Mandeville answered 14/8, 2016 at 21:32 Comment(0)
M
2

I could make it work like that:

Application class:

...
application.setRoles(getRoles(application));
... 
public static List<Role> getRoles(JaxRsApplication application) {
  List<Role> roles = new ArrayList<>();
  for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
      roles.add(new Role(application, authorizationRole.toString()));
  }
  return roles;
}
...

Authorization enum:

public enum AuthorizationRoleEnum {
  USER("user"),
  ADMIN("admin");

  private final String value;

  AuthorizationRoleEnum(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return value;
  }

}

At my resource classes:

...
@Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
  if (securityContext.getAuthenticationScheme() != null
    && !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
    throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
      .entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
  }
}
...
Mandeville answered 20/8, 2016 at 16:18 Comment(0)
B
2

It's not really clear (at least to me :-) ) what you are trying to achieve. If you have a class which is a subclass of javax.ws.rs.core.Application, you should be able to simply add @RolesAllowed("user") as an annotation to your resource classes, as shown in https://jersey.java.net/documentation/latest/security.html

@Path("/")
@PermitAll
public class Resource {
    @RolesAllowed("user")
    @GET
    public String get() { return "GET"; }

    @RolesAllowed("admin")
    @POST
    public String post(String content) { return content; }

    @Path("sub")
    public SubResource getSubResource() {
        return new SubResource();
    }
}

Accessing that resource should prompt you for your credentials. If that doesn't work, then you need to provide a small code sample, which compiles and doesn't do what you want it to do. Then it's easier to see where the problem is and what needs to be done to make it work

Bellis answered 18/8, 2016 at 17:46 Comment(5)
I've edited my question. Answer could be very simple but I couldn't find a way to make it work. I define the roles via restlet (it uses jax-rs extension) however it seems that it doesn't being recognized by jax-rs security context.Mandeville
if it gives you a 403 error, then it means that the authentication is working :-) You just need to add the user to the correct group. This is done in the application server security configuration. Did you set a breakpoint at the first line in getStatus() to see if it actually gets there?Bellis
Yes, authentication works. However authorization is not :) It really goes there but cannot get user's role to check. Everybody gets 403. It seems that jersey context doesn't recognize restlet user? However it isn't like that at here: restlet-discuss.1400322.n2.nabble.com/…Mandeville
What type of application server are you using? The mapping of users to roles is done in the application server.Bellis
I just use component.start(); to start the application. I don't have a web.xmlMandeville
M
2

I could make it work like that:

Application class:

...
application.setRoles(getRoles(application));
... 
public static List<Role> getRoles(JaxRsApplication application) {
  List<Role> roles = new ArrayList<>();
  for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
      roles.add(new Role(application, authorizationRole.toString()));
  }
  return roles;
}
...

Authorization enum:

public enum AuthorizationRoleEnum {
  USER("user"),
  ADMIN("admin");

  private final String value;

  AuthorizationRoleEnum(String value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return value;
  }

}

At my resource classes:

...
@Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
  if (securityContext.getAuthenticationScheme() != null
    && !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
    throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
      .entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
  }
}
...
Mandeville answered 20/8, 2016 at 16:18 Comment(0)
L
0

You need to implement your RoleChecker using this interface. As the doc says:

Because the Restlet API does not support its own mechanism for role checks (as e.g. the Servlet API), you must use this inteface if you need role checks in a JAX-RS application. This interface is used to check, if a user is in a role. Implementations must be thread save.

so as an example of implementation you can do smth like this:

  public class MyRoleChecker implements RoleChecker {
    public boolean isInRole(Principal principal, String role) {
      return principal.getRole().equals(role);
    } 
  }

Edited: On the other hand as you use the new API, you need to implement SecurityContext and inject it using @Context in your resource methods. Then you fetch roles list from the storage by username. The storage implementation is up to you. Please refer to this example

    @Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
    @Context
    UriInfo uriInfo;
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String authHeaderVal = requestContext.getHeaderString("Auth-Token");
        String subject = validateToken(authHeaderVal); //execute custom authentication
        if (subject!=null) {
            final SecurityContext securityContext = requestContext.getSecurityContext();
            requestContext.setSecurityContext(new SecurityContext() {
                        @Override
                        public Principal getUserPrincipal() {
                            return new Principal() {
                                @Override
                                public String getName() {
                                    return subject;
                                }
                            };
                        }
                        @Override
                        public boolean isUserInRole(String role) {
                            List<Role> roles = findUserRoles(subject);
                            return roles.contains(role);
                        }
                        @Override
                        public boolean isSecure() {
                            return uriInfo.getAbsolutePath().toString().startsWith("https");
                        }
                        @Override
                        public String getAuthenticationScheme() {
                            return "Token-Based-Auth-Scheme";
                        }
                    });
        }
    }
}
Liberalism answered 18/8, 2016 at 18:55 Comment(13)
Thanks for the answer. Seems reasonable. However is there any example about how to implement it?Mandeville
@Mandeville I edited my answer adding an example of implementation.Liberalism
I use org.restlet.ext.jaxrs version 2.2.3 but seems it doesn't have an interface named as RoleChecker?Mandeville
On the other hand Principal class does not have a getRole() method: http://docs.oracle.com/javase/7/docs/api/java/security/Principal.htmlMandeville
Right, that's old API, and you use the new one.Liberalism
Alsom seems that restlet documentation is wrong: restlet.com/technical-resources/restlet-framework/guide/2.2/… there is no way to for a call setRoleChecker().Mandeville
@Mandeville it's just obsolete. I edited my answer, please have a look.Liberalism
Is it org.restlet.security.User? It doesn't have getRole() method too. On the other hand I use jersey 1.9.Mandeville
@Mandeville I edited another way. Hope now it makes sense.Liberalism
Does jersey 1.9 has Priority? On the other hand from which package is User?Mandeville
@Mandeville it's still java.security.Principal used.Liberalism
OK. But it cannot find the Priority annotation and finds ContainerRequestFilter at com.sun.jersey.spi.container. It should be javax.ws.rs.container.ContainerRequestFilter however there is no container package at jersey 1.9.Mandeville
Lastly, I can access to header. However your example uses a token based authentication and accesses to that token. However, I don't have such a header at my example.Mandeville

© 2022 - 2024 — McMap. All rights reserved.