Testing security in RESTful application with jersey and jbehave

Security concerns emerges naturally in applications because softwares have so many capabilities and we sure don’t want these features to be available to anyone.
The important words in the above sentence are features and anyone. Securing an application is often a matter of 2 concepts :

  • identifying who should/should not access the system : aka Authentication. Solves the anyone problem.
  • when authenticated (or not), what can that subject do : aka Authorization. Solves the features availability problem.

With that in mind let’s explore what already exists in HTTP to handle these 2 concepts. Then we’ll review the technical solutions/frameworks available for security in java.

The http status 401 Unauthorized is meant to notify the requester that it is not recognized by the system as a valid user.

The http status 403 Forbidden is meant to notify the requester that it is recognized by the system as a valid user but is not granted a functionnality.

Again scenarii are no harm :

  • requesting a protected resource with wrong uid should return 401
  • requesting a protected resource with correct uid and wrong password should return 401
  • requesting a protected resource with correct uid, correct password and insufficient rights should return 403

Depending on your development context you’ll either go for one of these solutions (other exist but I think these are the 3 most used) :

  • container security (JEE) : authentication mecanism and authorization configuration are described in web.xml. Usually integrates well in JEE env.
  • spring-security : authentication mecanism and authorization configuration are described in a spring configuration (be it xml file or via annotations) loaded by web.xml. The main entry point is a filter.
  • apache shiro : same as spring-security. I have the feeling that shiro is simpler than spring-security. Its concepts are clear and well identified. The only important thing it doesn’t support is jsr-250 leaving it barely behind its opponents.

I have the feeling that the spring-security codebase quality tends to decrease. It really seems too complex and some feature (OAuth v1 to name it) are not correctly implemented but that’s not the point of the entry.

That’s it for the analysis, let’s practice.

I chose spring-security in my example because I know the framework. I’ve already invested in it for many years now. But I’ve never liked it that much because it’s always been a bit complex. Now it’s even worst. But I know the principles so it’s still faster for me to implement something than diving into another solution. Plus it still meets my needs. However it is a little more painful to use each time. For the above reasons I consider switching to Apache Shiro as soon as it supports jsr-250 security annotations (I really am a big fan of annotations).

1. Implement Authentication

Write a plain text story

... (omitted for brevity)
Scenario: requesting protected resource with wrong password should fail
Given I authenticate with bob uid and <password> password
And I accept <responseLanguage> language
When I request a protected resource
Then I should get an unsuccessful response
And the response code should be 401
And the response message should be <message>

Examples:
|password|responseLanguage|message|
||en|bad credentials provided|
|unknown-password|en|bad credentials provided|
||fr|informations d'identification incorrectes|
|unknown-password|fr|informations d'identification incorrectes|
... (omitted for brevity)

Create its JUnit Runner

...
public class AuthenticationStory extends JUnitStories {

	@Override
	public Configuration configuration() {

		Configuration configuration = new MostUsefulConfiguration();

		configuration.storyReporterBuilder() // Configure report builder
				.withFormats(Format.HTML_TEMPLATE, Format.ANSI_CONSOLE) // Configure
																		// desired
																		// output
																		// formats
				.withFailureTrace(true) //
				.withMultiThreading(true);

		configuration.storyControls().doSkipScenariosAfterFailure(false).doResetStateBeforeStory(true);

		return configuration;
	}

	@Override
	public InjectableStepsFactory stepsFactory() {
		return new InstanceStepsFactory(configuration(), new AuthenticationSteps());
	}

	@Override
	protected List<String> storyPaths() {
		return new StoryFinder().findPaths(CodeLocations.codeLocationFromClass(this.getClass()).getFile(),
				Arrays.asList("**/authentication.story"), null);
	}
}
...

Implement it as a POJO

...
	@When("I request a protected resource")
	public void requestProtectedResource() {
		final String path = "/advert/protected";
		final URI uri = URI.create(this.baseEndPoint + path);
		this.response = this.jerseyClient.resource(uri).acceptLanguage(new String[] { this.responseLanguage })
				.get(ClientResponse.class);
	}
...

Run the tests and note the failure : the protected resource doesn’t exist yet, you should get a 404 not found.

Then implement the controller

...
	@GET
	@Path("/protected")
	@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	public Response returnProtectedResource() throws Throwable {

		final Advert criteria = new Advert();

		// very important phone number !!!!!
		criteria.setPhoneNumber("0033606060606");

		final List<Advert> results = this.facade.findProtectedAdvertsByCriteria(criteria);

		final GenericEntity<List<Advert>> entity = new GenericEntity<List<Advert>>(results) {
		};

		if (CollectionUtils.isEmpty(results)) AdvertController.LOGGER.info("No results found");

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

	}
...

Run the tests and note the failure : the protected resource exists, you should get a 200 but you expect a 401.

Implement security in 4 steps

– declare a security filter and its mapping to web.xml

...
	<filter>
		<filter-name>authenticationFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>authenticationFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
...

– define an authentication mechanism
Mechanism declaration in spring configuration

...
    <sec:http entry-point-ref="defaultEntryPoint" auto-config="true"
              realm="diveintojee.org">
        <sec:http-basic/>
    </sec:http>
...

Mechanism implementation. I needed one as the default one did not suit me mainly due to i18n

...
    /**
     * @see org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#commence(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, org.springframework.security.core.AuthenticationException)
     */
    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
                         final AuthenticationException authException) throws IOException, ServletException {
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
        response.setStatus(exceptionConverter.resolveHttpStatus(authException));
        final PrintWriter writer = response.getWriter();
        final String i18nMessage = exceptionConverter.resolveMesage(request, authException);
        writer.println(i18nMessage);
    }
...

– configure users and roles to match against provided credentials

...
    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="bob" password="bob" authorities="ROLE_USER"/>
                <sec:user name="visitor" password="visitor" authorities="ROLE_USER,ROLE_ADMIN"/>
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
...

There are 2 extra steps related to exception handling :

– map AuthenticationException to 401 code (for a details about handling errors in rest you can read this post)

...
    public int resolveHttpStatus(final Throwable th) {
        if (th == null)
            return HttpServletResponse.SC_OK;
        // th.printStackTrace();
        if (th instanceof NotFoundException)
            return HttpServletResponse.SC_NOT_FOUND;
        if (th instanceof AuthenticationException)
            return HttpServletResponse.SC_UNAUTHORIZED;
        if (th instanceof AccessDeniedException)
            return HttpServletResponse.SC_FORBIDDEN;
        if (th instanceof IllegalArgumentException || th instanceof ValidationException
            || th instanceof BusinessException)
            return HttpServletResponse.SC_BAD_REQUEST;
        if (th instanceof IllegalStateException)
            return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        if (th instanceof WebApplicationException && ((WebApplicationException) th).getResponse() != null)
            return ((WebApplicationException) th).getResponse().getStatus();
        return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
    }
...

– translate message in your messages source

404=Advert with reference {0} was not found
403=Access denied

2. Implement Authorization

Repeat the above steps.

Enable jsr-250 security annotations

...
<sec:global-method-security jsr250-annotations="enabled"/>
...

Protect your methods

...
	/**
	 * @see org.diveintojee.poc.jbehave.domain.business.Facade#findProtectedAdvertsByCriteria(org.diveintojee.poc.jbehave.domain.Advert)
	 */
	@RolesAllowed("ROLE_ADMIN")
	@Override
	public List<Advert> findProtectedAdvertsByCriteria(Advert criteria) {
		return Collections.emptyList();
	}
...

You’re done.

Now that you behaviors act like a shield you’re more confident to try different solutions (container/spring/shiro), different authentication mechanisms (http basic/http digest/oauth v1/oauth v2).

I pushed the complete source on github.

Feel free to fork, tweet, comment, react share or whatever pleases you as soon as it remains constructive.

Cheers.

Advertisements

2 thoughts on “Testing security in RESTful application with jersey and jbehave

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s