Hello, I am trying to create an application where I can mix Java EE
security annotations in JAX-RS endpoints. I am using current TomEE 2
Snapshot and basically I am creating two ContainerRequestFilter.
One for authentication and another for authorization.

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

    private static final List<Class<? extends Annotation>>
securityAnnotations =
        Arrays.asList(DenyAll.class, PermitAll.class, RolesAllowed.class);

    @Context
    private ResourceInfo resourceInfo;

    private UserService userservice = new UserService();

    @Override
    public void filter(ContainerRequestContext request) throws IOException {
        if (isSecuredResource()) {

            String token = request.getHeaderString("x-access-token");

            try {
                String username = getUsernameFromToken(token);
                final User user = userservice.findUser(username);

                request.setSecurityContext(new SecurityContext() {

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

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

                    @Override
                    public Principal getUserPrincipal() {
                        return user;
                    }

                    @Override
                    public String getAuthenticationScheme() {
                        return SecurityContext.BASIC_AUTH;
                    }
                });

            } catch (java.text.ParseException | JOSEException |
NullPointerException e) {
                request.abortWith(Response.status(404).build());
            }
        }
    }

    private boolean isSecuredResource() {

        for (Class<? extends Annotation> securityClass :
securityAnnotations) {
            if (resourceInfo.getResourceMethod().isAnnotationPresent(
                securityClass)) {
                return true;
            }
        }

        for (Class<? extends Annotation> securityClass :
securityAnnotations) {
            if (resourceInfo.getResourceClass().isAnnotationPresent(
                securityClass)) {
                return true;
            }
        }

        return false;
    }

    private String getUsernameFromToken(String token)
            throws java.text.ParseException, JOSEException {

        SignedJWT signedJWT = SignedJWT.parse(token);
        JWSVerifier verifier = new MACVerifier(SharedSecret.getSecret());

        if (signedJWT.verify(verifier)) {
            return signedJWT.getJWTClaimsSet().getSubject();
        } else {
            throw new JOSEException("Firm is not verified.");
        }
    }
}


note that I am creating a custom security context and for authorization:

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

  private static final Response NOT_FOUND = Response.status(
      Response.Status.NOT_FOUND).entity("{\"message\": \"Resource Not
Found\"}").build();

  @Context
  private ResourceInfo resourceInfo;

  @Override
  public void filter(ContainerRequestContext requestContext)
      throws IOException {
    Method resourceMethod = resourceInfo.getResourceMethod();

    // DenyAll on the method take precedence over RolesAllowed and PermitAll
    if (resourceMethod.isAnnotationPresent(DenyAll.class)) {
      requestContext.abortWith(NOT_FOUND);
      return;
    }

    // RolesAllowed on the method takes precedence over PermitAll
    RolesAllowed ra = resourceMethod.getAnnotation(RolesAllowed.class);
    if(assertRole(requestContext, ra)) {
      return;
    }

    // PermitAll takes precedence over RolesAllowed on the class
    if (resourceMethod.isAnnotationPresent(PermitAll.class)) {
      // Do nothing.
      return;
    }

    if (resourceInfo.getResourceClass().isAnnotationPresent(DenyAll.class))
{
      requestContext.abortWith(NOT_FOUND);
    }

    // RolesAllowed on the class takes precedence over PermitAll
    ra = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
    if(assertRole(requestContext, ra)) {
      return;
    }
  }

  private boolean assertRole(ContainerRequestContext requestContext,
RolesAllowed ra) {

    if (ra != null) {
      String[] roles = ra.value();
      for (String role : roles) {
        if (requestContext.getSecurityContext().isUserInRole(role)) {
          return true;
        }
      }
      requestContext.abortWith(NOT_FOUND);
    }
    return false;
  }
}

And the business code:

@Stateless
@Path("/app")
public class UiApplication {

    @Inject
    UserService userService;

    @Inject
    MovieService moviesService;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response login(JsonObject jsonObject) throws JOSEException {

      JsonString username = (JsonString) jsonObject.get("username");
      JsonString password = (JsonString) jsonObject.get("password");

      if (authenticate(username.getString(), password.getString())) {

        String token = createToken(username.getString(), "example.com");

        JsonObject responseDocument = Json.createObjectBuilder()
          .add("user", Json.createObjectBuilder().add("username",
username).build())
          .add("token", token)
          .build();

        return Response.ok(responseDocument).build();

      }

      return Response.status(Status.NOT_FOUND).build();

    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("admin")
    public Response movies() {
        return Response.ok("{\"message\":\"a\"}").build();
    }

    private boolean authenticate(String username, String password) {
        return this.userService.findUser(username, password);
    }

    private String createToken(String subject, String issuer) throws
JOSEException {

      JWSSigner signer = new MACSigner(SharedSecret.getSecret());

      JWTClaimsSet claimsSet = new JWTClaimsSet();
      claimsSet.setSubject(subject);
      claimsSet.setIssueTime(new Date());
      claimsSet.setIssuer(issuer);

      SignedJWT signedJWT = new SignedJWT(new
JWSHeader(JWSAlgorithm.HS256), claimsSet);
      signedJWT.sign(signer);

      return signedJWT.serialize();

    }

}

If I run in this way it doesn't work because the endpoint is also an EJB
and because security context of EJB is not shared with the JAX-RS, the
method throws an 403 Forbidden. Is there any clean way to make that this
don't happen? Of course I can make JAXRS class as none EJB, but this would
be my last option.

Also is there a way to use @Inject in ContainerRequestFilter? Currently I
cannot use it because the instance is not injected.

Thank you so much.

Alex.

Reply via email to