Endpoints with Jersey.
I want to secure an endpoint with a ContainerRequestFilter
@Provider
@Secured
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
//TODO: on logger here...
System.out.printf("Filtering %s request... AuthorizationRequestFilter\n", requestContext.getMethod());
requestContext.getHeaders().add("X-Secured-By", "Jersey >_<");
System.out.printf("SecurityContext: %s (%s).\n", securityContext, securityContext.getAuthenticationScheme());
if (securityContext == null || !securityContext.isUserInRole("privileged")) {
requestContext.abortWith(new UnauthorizedResponse().getResponse());
}
}
}
The annotation @Secured
:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Secured {}
So I can do this:
@Path("foobar")
public class FooResource {
//...
@Context
SecurityContext securityContext;
//...
@GET
@Secured
@Path(value = "foo")
@Produces(MediaType.APPLICATION_JSON)
public Response getFoo(@Context SecurityContext sc, @Context UriInfo ui, @Context HttpHeaders hh) {
// ...
}
//...
And I'm doing it right (I think), because with my test I don't even pass through the getFoo
endpoint but is the ContainerRequestFilter that kicks me out. In fact I receive this (the "X-Secured-By" header is hand-made):
Headers: {X-Secured-By=[Jersey >_< kicked you out!], Content-Length=[97], Date=[Wed, 03 Dec 2014 17:46:50 GMT], Content-Type=[application/json], X-Powered-By=[Jersey ^_^]}
Response: InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:9998/urler/test, status=401, reason=Unauthorized}}
Now it would be nice to mock the SecurityContext
.
This is what I'm doing... and if I'm here, it's obviously silly and/or wrong.
public class UrlerResourceTest extends JerseyTest {
//....
@Override
public TestContainerFactory getTestContainerFactory() {
GrizzlyTestContainerFactory grizzlyTestContainerFactory = new GrizzlyTestContainerFactory();
System.out.printf("The GrizzlyTestContainerFactory: %s ", grizzlyTestContainerFactory);
// just for debugging...
return grizzlyTestContainerFactory;
}
@Test
public void testSecuredEndpoint() throws JSONException {
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.isUserInRole(anyString())).thenReturn(true);
Mockito.when(securityContext.getAuthenticationScheme()).thenReturn("Just Mocking...");
ReflectionTestUtils.setField(resource, "securityContext", securityContext, SecurityContext.class);
final Response response = target("foobar")
.path("foo")
.request(MediaType.APPLICATION_JSON)
.get();
System.out.println(getFormattedStringResponseInfo(response));
JSONObject entity = new JSONObject(response.readEntity(String.class));
assertTrue(entity.get("secured").equals(true));
assertTrue(response.getHeaders().containsKey("X-Secured-By"));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
}
How can I mock the SecurityContext
in my tests?
Thank you so much in advance.
While doing unit testing using junit you will come across places where you want to mock classes. Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls.
Disclaimer: I'm not really a Mockito user, but from what I understand, mocking is used for situations where you have injected class dependencies (fields), and you mock those dependencies. In which case you still need to set the field with the mocked object. For example
public class TestClass {
TestService testService;
public void doTest() {
System.out.println(testService.getString());
}
public void setTestService(TestService testService) {
this.testService = testService;
}
}
public class TestService {
public String getString() {
return "Hello world";
}
}
@Test
public void toTest() {
TestService testService = Mockito.mock(TestService.class);
Mockito.when(testService.getString()).thenReturn("Hello Squirrel");
TestClass testClass = new TestClass();
testClass.setTestService(testService);
testClass.doTest();
}
You can see we are setting the the TestService
in the TestClass
with the mocked object. It's not greatest example, as we could simple instantiate TestService
, but it shows, from my understanding, how the mocking should work.
That being said, I don't see how it is possible to do this with the AuthorizationRequestFilter
, as it's handled by the test container, and we are not instantiating it for a unit test. Even if we were, it would seem intrusive (and redundant) to add a SecurityContext
field.
So without a full integration test, where we are starting the server, and using the server's authentication capabilities, it will be difficult to handle the SecurityContext
per this use case, as the SecurityContext
is created by the container, taking information from the underlying servlet containers authentication mechanism.
One way you can achieve this though (which IMO doesn't seem very elegant - but works), without a full integration test, is to create a a filter which performs before your AuthorizationRequestFilter
, and set the SecurityContext
from there. Testing aside, this is actually pretty common in cases where we need to implement outr own custom authentication mechanism.
An example of how you might do this for your unit test, might be something like:
public class UrlerResourceTest extends JerseyTest {
...
@Override
public Application configure() {
return new ResourceConfig(FooResource.class)
.register(AuthorizationRequestFilter.class)
.register(AuthenticationFilter.class);
}
@Provider
@Priority(Priorities.AUTHENTICATION)
public static class AuthenticationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return "Stackoverflow";
}
};
}
@Override
public boolean isUserInRole(String string) {
return "privileged".equals(string);
}
@Override
public boolean isSecure() { return true; }
@Override
public String getAuthenticationScheme() { return "BASIC"; }
});
}
}
...
}
This filter will perform before the AuthorizationRequestFilter
because of the @Priority
annotation. We've set it to Priorities.AUTHENTICATION
which will before before any other filter without such annotation. (See Priorities API and Priorities with Jersey. Also the SecurityContext
will be passed along between filters and also be injected into your resource class.
As I said, I don't think this is very elegant to have to create another filter, but it works for this purpose. Also I am not too familiar with the Jersey Test Framework, as I'm still beginning with it, but there are many configuration options for deployment within a servlet context. I don't know if we can configure the needed authentication mechanism for this case, but it might be something worth looking into.
Edit: In the beginning I explained about setting the field for the test object, but we can also pass the mocked object to a method. For example we could mock the ContainterRequestContext
in the filter
method, and call filter
ourselves, passing the mocked ContainerRequestContext
. But this is only useful when we are actually unit testing the filter class and instantiating it ourselves, which is not the case here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With