kamaci
kamaci

Reputation: 75137

Jax RS Authorization

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.

Upvotes: 9

Views: 2808

Answers (3)

kamaci
kamaci

Reputation: 75137

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());
  }
}
...

Upvotes: 2

Battle_Slug
Battle_Slug

Reputation: 2105

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";
                        }
                    });
        }
    }
}

Upvotes: 0

Guenther
Guenther

Reputation: 2045

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

Upvotes: 2

Related Questions