1

I have a Rest based service using a ContianerRequestFilter (AuthFilter below) to validate a user or their token. Everything at that level works fine as the user is authorized or not authorized as expected. The question is how to do get the user info in the resource layer? For instance if a user requests a list of areas in AreasResource (below), how can I get the user info and use that to constrain the results return to him/her?

AuthFilter:

@Provider
@PreMatching
public class AuthFilter implements ContainerRequestFilter
{
    @Autowired
    IAuthenticator authenticator;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException
    {
        //PUT, POST, GET, DELETE...
        String method = requestContext.getMethod();

        String path = requestContext.getUriInfo().getPath(true);

        UserWrapper authenticationResult = null;
        Date expireTime = new Date(new Date().getTime() + 60 * 1000);
        if (!"init".equals(path))
        {
            if ("GET".equals(method) && ("application.wadl".equals(path) || "application.wadl/xsd0.xsd".equals(path)))
            {
                return;
            }

            String auth = requestContext.getHeaderString("authorization");
            if(auth == null) 
            {
                throw new WebApplicationException(Status.UNAUTHORIZED);
            }

            if (auth.startsWith("Bearer")) 
            {
                String token = auth.substring("Bearer".length()).trim();
                try 
                {
                    authenticationResult = validateToken(token);
                }
                catch (Exception e)
                {
                    throw new WebApplicationException(Status.UNAUTHORIZED);
                }
            }
            else 
            {
                //lap: loginAndPassword
                String[] lap = BasicAuth.decode(auth);
                if (lap == null || lap.length != 2)
                {
                    throw new WebApplicationException(Status.UNAUTHORIZED);
                }

                // Handle authentication validation here
                authenticationResult = authenticator.authenticatUser(lap);

                // if null then user can't be found or user name and password failed
                if (authenticationResult == null)
                {
                    throw new WebApplicationException(Status.UNAUTHORIZED);
                }
            }
        }
        else 
        {
            authenticationResult = new UserWrapper(new User(), expireTime.getTime());
        }

        // We passed so we put the user in the security context here
        String scheme = requestContext.getUriInfo().getRequestUri().getScheme();
        requestContext.setSecurityContext(new ApplicationSecurityContext(authenticationResult, scheme));
    }

private UserWrapper validateToken(String token) throws Exception
{
    UserWrapper userWrapper = AuthenticatorCache.getInstance().getObj(token);
    if (userWrapper == null)
    {
        throw new Exception("No session found");
    }
    return userWrapper;
    }
}

Areas Resource:

@Path("/areas")
@Component
@Api(value = "/areas" )
public class AreasResource implements IAreas 
{
    @Override
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response listActiveAreas() {
        return Response.ok('woo hoo it worked').build();
    }
}
supertorqued
  • 109
  • 1
  • 3
  • 15

2 Answers2

3

Overriding the SecurityContext

One possible way to achieve it is overriding the SecurityContext of the ContainerRequestContext in your ContainerRequestFilter implementation. It could be something as following:

requestContext.setSecurityContext(new SecurityContext() {

    @Override
    public Principal getUserPrincipal() {

        return new Principal() {

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

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

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

    @Override
    public String getAuthenticationScheme() {
        return null;
    }
});

Then the SecurityContext can be injected in any resource class using the @Context annotation:

@Path("/example")
public class MyResource {

    @Context
    private SecurityContext securityContext;

    ...
}

It alson can be injected in a resource method parameter:

@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myResourceMethod(@PathParam("id") Long id, 
                                 @Context SecurityContext securityContext) {
    ...
}

And then get the Principal from the SecurityContext:

Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();

I have initially described this approach in this answer.

Alternatives

If you don't want to override the SecurityContext for some reason, you could consider other approaches, depending on what you have available in your application:

  • With CDI, you could create a bean annotated with @RequestScoped to hold the name of the authenticated user. After performing the authentication, set the name of the user in the request scoped bean and inject it into your resource classes using @Inject.

  • Since you are using Spring, you could consider using Spring Security on the top of your JAX-RS application for authentication and authorization.

Community
  • 1
  • 1
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • 1
    Thanks to both Cassio and Vadim for you answers. This worked best for me as I was already overiding SecurityContext. Making @Context SecurityContext securityContext a param did the trick. – supertorqued Feb 15 '17 at 00:33
  • @supertorqued I'm glad to know that it worked. Please, don't forget to upvote our answers :) – cassiomolin Feb 15 '17 at 06:26
  • Glad to hear it works. BTX Inject it as method parameter is always better. Have it as instance variable requires Resource class to be @RequestScoped - to avoid multi-threading problems. – Vadim Feb 17 '17 at 01:40
1

I guess you need to add your SecurityContext as parameter to the method annotated as @Context like that:

@Override
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(@Context SecurityContext securityCtx) {

    // Do something with data in securityCtx...

    return Response.ok("woo hoo it worked").build();
}

If it will not work (I did not try it and use other way): You may set your securityContext as HttpRequest/HttpServletRequest or (Session) attribute with some name (as example user.security.ctx) and inject Request same way. i.e.

@Override
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(@Context HttpServletRequest request) {

    SecurityContext securityCtx = (SecurityContext )request.getAttribute("user.security.ctx");

    // Do something with data in securityCtx...

    return Response.ok("woo hoo it worked").build();
}
Vadim
  • 4,027
  • 2
  • 10
  • 26