Tuesday, October 14, 2014

Implementing JSR-250 hierarchical roles in SpringSecurity

Get the code: https://github.com/iainporter/oauth2-provider

In a previous post I walked through setting up an OAuth2 provider service using Spring Security. I used JSR-250 role-based annotations to protect access to resources. An example of this is the /v1.0/me API to return user details:

    @RolesAllowed({"ROLE_USER"})
    @GET
    public ApiUser getUser(final @Context SecurityContext securityContext) {
        User requestingUser = loadUserFromSecurityContext(securityContext);
        if(requestingUser == null) {
            throw new UserNotFoundException();
        }
        return new ApiUser(requestingUser);
    }


What happens when we create a new role for admin users called ROLE_ADMIN?
This user should have the same access rights as ROLE_USER as well as other restrictive rights not available to regular users. The temptation would be to simply add another role to the list of allowed roles:

@RolesAllowed({"ROLE_USER", "ROLE_ADMIN"})

The better solution would be to keep the access as ROLE_USER but allow anyone with a role that extends ROLE_USER to access the resource. To do that we would have to implement hierarchical roles and the access decision mechanism in Spring Security would need to know how to handle this hierarchy.

Creating an access controlled resource

I'll create a simple service that can be accessed by anyone with the role of ROLE_GUEST. Users with a higher level of access will have a role of ROLE_USER and can access anything that ROLE_GUEST users can. The url of the resource will be /v1.0/samples.

@Path("/v1.0/samples")
@Component
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public class SampleResource extends BaseResource {

    @RolesAllowed({"ROLE_GUEST"})
    @GET
    public Response getSample(@Context SecurityContext sc) {
        User user = loadUserFromSecurityContext(sc);
        return Response.ok().entity("{\"message\":\"" + user.getEmailAddress() + 
                  " is authorized to access\"}").build();
    }
}

The resource is in a package that Jersey does not yet know about so it has to be registered with the com.porterhead.RestResourceApplication class

packages("com.porterhead.resource", "com.porterhead.user.resource", "com.porterhead.sample");


Next Spring needs to know about the class in order to apply method level security so we need to add the package to component scanning in application-context.xml:

<context:component-scan base-package="com.porterhead.sample"/>

Writing a failing functional test


Before implementing the changes I'll add a failing functional test that will register a user and then attempt to access the service.
This should result in a 401 status as users are assigned a ROLE_USER role on registration and the system does not know anything about ROLE_GUEST.

    public void testHierarchicalRole() {

        //sign up a user with role of ROLE_USER
        def username = createRandomUserName()
        httpSignUpUser(getCreateUserRequest(username, TEST_PASSWORD))

        //login and get the oauth token
        def loginResponse = httpGetAuthToken(username, TEST_PASSWORD)

        //get the resource that requires a role of ROLE_GUEST
        def sampleResponse = getRestClient().get(path: "/v1.0/samples", 
              contentType: ContentType.JSON, headers: ['Authorization': "Bearer " 
              + loginResponse.responseData["access_token"])

        assertEquals(200, sampleResponse.status)

        return Response.ok().entity("{\"message\":\"" + username.toLowerCase() 
              + " is authorized to access\"}").build();
    }

Execute the tests:

./gradlew integrationTest

This will result in ONE failure which you can view at

  oauth2-provider/build/reports/tests/index.html.

You can also try it out with curl
Start the application by executing ./gradlew tomcatRun and ensure you have mongodb running

First create a user:

   curl -v -X POST -H "Content-Type: application/json" \
   -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
   -d '{"user":{"emailAddress":"foo@example.com"}, "password":"password"}' \
   'http://localhost:8080/oauth2-provider/v1.0/users'


Extract the access token from the response and send the request for samples:

curl -v -X GET \    -H "Content-Type: application/json" \
   -H "Authorization: Bearer <the access token from user registration>" \
   'http://localhost:8080/oauth2-provider/v1.0/samples'


You should see a response similar to this:

   HTTP/1.1 401 Unauthorized
   Server Apache-Coyote/1.1 is not blacklisted
   Server: Apache-Coyote/1.1
   Content-Type: application/json
   Content-Length: 168
   Date: Tue, 14 Oct 2014 19:10:26 GMT

   {"errorCode":"401","consumerMessage":"You do not have the appropriate privileges to access this resource","applicationMessage":"Access is denied","validationErrors":[]}


The Role hierarchy class


We only need a simple hierarchy with ROLE_ADMIN extending ROLE_USER which in turn extends ROLE_GUEST.
Fortunately we don't have to do too much work as Spring already has an implementation that is fit for this purpose. We just need to add a bean definition with our role hierarchies to security-configuration.xml.

    <bean id="roleHierarchy"
          class="org.springframework.security.access.hierarchicalroles.
                   RoleHierarchyImpl">
        <property name="hierarchy">
            <value>
                ROLE_ADMIN > ROLE_USER
                ROLE_USER > ROLE_GUEST
            </value>
        </property>
    </bean>


Customising JSR-250 Voter class

The spring implementation of JSR-250 is almost there but does not know how to handle hierarchies. Spring has another class (RoleHierarchyVoter) that knows how to handle hierarchical roles so we can use some of that to extract the roles.

So if we subclass Jsr250Voter and modify the vote method so that it extracts the hierarchical roles that should do the trick.

public class HierarchicalJsr250Voter extends Jsr250Voter {

    private RoleHierarchy roleHierarchy = null;

    public HierarchicalJsr250Voter(RoleHierarchy roleHierarchy) {
        Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
        this.roleHierarchy = roleHierarchy;
    }

    @Override
    public int vote(Authentication authentication, Object object, 
                     Collection<ConfigAttribute> definition) {
        boolean jsr250AttributeFound = false;

        for (ConfigAttribute attribute : definition) {
            if (Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE.equals(attribute)) {
                return ACCESS_GRANTED;
            }

            if (Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE.equals(attribute)) {
                return ACCESS_DENIED;
            }

            if (supports(attribute)) {
                jsr250AttributeFound = true;
                // Attempt to find a matching granted authority
                for (GrantedAuthority authority : extractAuthorities(authentication)) {
                    if (attribute.getAttribute().equals(authority.getAuthority())) {
                        return ACCESS_GRANTED;
                    }
                }
            }
        }

        return jsr250AttributeFound ? ACCESS_DENIED : ACCESS_ABSTAIN;
    }

    Collection<? extends GrantedAuthority> extractAuthorities(
           Authentication authentication) {
        return roleHierarchy.getReachableGrantedAuthorities(
               authentication.getAuthorities());
    }


Wire the new class into the application context


Adding the bean definition to security-configuration.xml:

    <bean id="roleVoter" class="com.porterhead.security.HierarchicalJsr250Voter">
        <constructor-arg ref="roleHierarchy" />
    </bean>


And the final piece of the puzzle is to change the bean that is being referenced by the AccessDecisionManager:

    <bean id="accessDecisionManager" 
          class="org.springframework.security.access.vote.UnanimousBased"
          xmlns="http://www.springframework.org/schema/beans">
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter"/>
            </list>
        </property>
    </bean>

Testing the changes

First make sure the integration test passes by executing ./gradlew integrationTest

Testing with curl should produce a response similar to this:

   HTTP/1.1 200 OK
   Server Apache-Coyote/1.1 is not blacklisted
   Server: Apache-Coyote/1.1
   Content-Type: application/json
   Content-Length: 42
   Date: Tue, 14 Oct 2014 19:54:28 GMT

  {"message":"You are authorized to access"}


Other posts in this series



Saturday, May 31, 2014

Securing REST Services with Spring Security and OAuth2

Get the code: https://github.com/iainporter/oauth2-provider

This post will walk through setting up an OAuth2 provider service for protecting access to REST resources. The code is available in github. You can fork the code and start writing services that will be protected by OAuth access. This is a separate module but builds on services covered in a previous series that includes:


It would be useful but not required to be familiar with some of the technologies covered such as:

* OAuth2 Protocol
* Spring Security
* Spring Integration
* Spring Data
* Jersey/JAX-RS
* Gradle / Groovy
* MongoDB

Resource Owner Password Flow

Subsequent posts will deal with the other types of authorization flow, such as using third party providers (Facebook, Google, etc). The intent of this post is a walk through of the Resource Owner Password flow. This is a typical use case if you are the system of record for user credentials and trust client applications. It is simply the exchange of the user's username and password for an access token. This token can then be used on subsequent requests to authorize access to resources. It is also important to support token expiration and by extension token refresh.

Let's test out the code and then I'll walk through the application and explain how it is built

Building the project


Check out the source code


> git clone  git@github.com:iainporter/oauth2-provider.git

> cd oauth2-provider

> ./gradlew clean build integrationTest

Running the Web Application


The application uses MongoDB as the persistence store. Before running the application ensure that mongod is running on port 27017. If you don't have mongo installed get it here

Once mongoDB is installed and running fire up the web application using the following command

> ./gradlew tomcatRun


To try it out you can open a browser window and navigate to http://localhost:8080/oauth2-provider/index.html

Testing with Curl


To create a user:

> curl -v -X POST \
   -H "Content-Type: application/json" \
   -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
   -d '{"user":{"emailAddress":"user@example.com"}, "password":"password"}' \
   'http://localhost:8080/oauth2-provider/v1.0/users'

The result:

{"apiUser":
   {"emailAddress":"user@example.com",
   "firstName":null,
   "lastName":null,
   "age":null,
   "id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",
   "name":"user@example.com"},
   "oauth2AccessToken":
   {"access_token":"7e0e4708-7837-4a7e-9f87-81c6429b02ac",
   "token_type":"bearer",
   "refresh_token":"d0f248ab-e30f-4a85-860c-bd1e388a39b5",
   "expires_in":5183999,
   "scope":"read write"
   }
}


Requesting an access token:


> curl -v -X POST \
   -H "Content-Type: application/json" \
   -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
   'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&username=user@example.com&password=password'


The result:

{
  "access_token":"a838780e-35ef-4bd5-92c0-07a45aa74948",
  "token_type":"bearer",
  "refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
  "expires_in":5183999
}


Refreshing a token:


> curl -v -X POST \
   -H "Content-Type: application/json" \
   -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
   'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=ab06022f-247c-450a-a11e-2ffab116e3dc'


The result:

{
   "access_token":"4835cd11-8bb7-4b76-b857-55c6e7f36fc4",
   "token_type":"bearer",
   "refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
   "expires_in":5183999
}


That's all that is needed to support registration of Users and managing tokens on their behalf. The source code also contains everything needed to support lost password and email verification. See previous posts for how to set it up.

In a typical deployment the authorization server and the resource server(s) would be separate, but for the purposes of this tutorial they are managed within the same application.
Let's delve into the details:

Web Context


There are two servlets.

A Jersey servlet as the default to handle all resource calls:
<servlet-mapping>
        <servlet-name>jersey-servlet</servlet-name>
        <url-pattern>/*</url-pattern>
</servlet-mapping>
A Spring servlet to handle all oauth calls:
<servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
Wire in spring security by defining a filter:
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>contextAttribute</param-name>
            <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
        </init-param>
    </filter>
and then map it to the root context so all calls are filtered:
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Configuring OAuth flows to support


<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices">
        <oauth:refresh-token/>
        <oauth:password/>
    </oauth:authorization-server>

In this scenario only password flow and refresh token are being supported. The default token endpoint is /oauth/token.

Protecting the token endpoint


It is a good idea to protect access to token requests to only those client applications that you know about. Using Spring security we can set up basic authentication on calls to the token endpoint:
    <http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"
          xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false"/>
        <http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
        <access-denied-handler ref="oauthAccessDeniedHandler"/>
    </http>

Next we configure the authentication manager and client details service
    <bean id="clientCredentialsTokenEndpointFilter"
          class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
        <property name="authenticationManager" ref="clientAuthenticationManager"/>
    </bean>

    <authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
        <authentication-provider user-service-ref="client-details-user-service"/>
    </authentication-manager>


    <bean id="client-details-user-service" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
        <constructor-arg ref="client-details-service" />
    </bean>

Depending on how many clients we expect to access the service will determine what type of client details service we implement. A simple file-based service might be sufficient. If you wanted to go a lot further with client sign up and managing API keys then you would have to involve a persistence tier for the client details service. Configuring with the default Spring file-based service is trivial:

<oauth:client-details-service id="client-details-service">

        
        <oauth:client
                client-id="353b302c44574f565045687e534e7d6a"
                secret="286924697e615a672a646a493545646c"
                authorized-grant-types="password,refresh_token"
                authorities="ROLE_TEST"
                access-token-validity="${oauth.token.access.expiresInSeconds}"
                refresh-token-validity="${oauth.token.refresh.expiresInSeconds}"
                />

        
        <oauth:client
                client-id="7b5a38705d7b3562655925406a652e32"
                secret="655f523128212d6e70634446224c2a48"
                authorized-grant-types="password,refresh_token"
                authorities="ROLE_WEB"
                access-token-validity="${oauth.token.access.expiresInSeconds}"
                refresh-token-validity="${oauth.token.refresh.expiresInSeconds}"
                />

        
        <oauth:client
                client-id="5e572e694e4d61763b567059273a4d3d"
                secret="316457735c4055642744596b302e2151"
                authorized-grant-types="password,refresh_token"
                authorities="ROLE_IOS"
                access-token-validity="${oauth.token.access.expiresInSeconds}"
                refresh-token-validity="${oauth.token.refresh.expiresInSeconds}"
                />

        
        <oauth:client
                client-id="302a7d556175264c7e5b326827497349"
                secret="4770414c283a20347c7b553650425773"
                authorized-grant-types="password,refresh_token"
                authorities="ROLE_ANDROID"
                access-token-validity="${oauth.token.access.expiresInSeconds}"
                refresh-token-validity="${oauth.token.refresh.expiresInSeconds}"
                />

</oauth:client-details-service>

Accessing the oauth endpoint now requires a basic authentication header with the client id and secret concatenated with a ":" separator and base64 encoded.
e.g. -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM="

Configuring User Authentication Services


The Resource Owner Password flow requires an authentication manager for managing users.

    <bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>

    <sec:authentication-manager alias="userAuthenticationManager">
        <sec:authentication-provider user-service-ref="userService">
            <sec:password-encoder ref="passwordEncoder"/>
        </sec:authentication-provider>
    </sec:authentication-manager>

The password encoder is used to encrypt the password on authentication. The user service also uses the same encoder to encrypt passwords before persisting them.
The user service must implement UserDetailsService and retrieve the user by username.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        notNull(username, "Mandatory argument 'username' missing.");
        User user = userRepository.findByEmailAddress(username.toLowerCase());
        if (user == null) {
            throw new AuthenticationException();
        }
        return user;
    }

Configuring Token Services


The final piece of configuration is to manage storage and retrieval of access tokens.

    <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
        <property name="tokenStore" ref="tokenStore"/>
        <property name="supportRefreshToken" value="true"/>
        <property name="clientDetailsService" ref="client-details-service"/>
    </bean>

Spring has a handy in-memory implementation that is useful when testing:

     <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore"/>

For this tutorial I have chosen MongoDB as the persistence technology, but it would be easy to wire in an alternative such as MySql or Redis It is a matter of implementing org.springframework.security.oauth2.provider.token.TokenStore

<bean id="tokenStore" class="com.porterhead.oauth2.mongodb.OAuth2RepositoryTokenStore">
      <constructor-arg ref="OAuth2AccessTokenRepository"/>
      <constructor-arg ref="OAuth2RefreshTokenRepository"/>
</bean>

Protecting access to resources


First we need to configure a spring resource server filter. This will check that there is a valid access token in the request header.

    <oauth:resource-server id="resourceServerFilter" token-services-ref="tokenServices"/>

Spring security has some useful classes for fine-grain control of roles and permissions. I prefer to use JSR-250 annotations. Luckily it is easy to wire this in. First we need to enable JSR-250 annotations with the following configuration:

    <sec:global-method-security jsr250-annotations="enabled" access-decision-manager-ref="accessDecisionManager"/>

Configure an access manager that uses the Jsr250Voter

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
        <property name="decisionVoters">
            <list>
                <bean class="org.springframework.security.access.annotation.Jsr250Voter"/>
            </list>
        </property>
    </bean>

Now we can protect REST resource methods with JSR-250 annotations such as @RolesAllowed

Trying it out


Typically the glue between the OAuth server and the application is a user identifier. When a client gets an access token for a user the next step is to typically load data related to that user. This will usually involve building a url with the userId as part of the path
i.e. /v1.0/users/{id}/someresource
A useful service to provide is a way to get user information based on the access token. The application can identify the user that owns the token and return information on that user to the client.
@Path("/v1.0/me")
@Component
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public class MeResource extends BaseResource {

    @RolesAllowed({"ROLE_USER"})
    @GET
    public ApiUser getUser(final @Context SecurityContext securityContext) {
        User requestingUser = loadUserFromSecurityContext(securityContext);
        if(requestingUser == null) {
            throw new UserNotFoundException();
        }
        return new ApiUser(requestingUser);
    }

    protected User loadUserFromSecurityContext(SecurityContext securityContext) {
        OAuth2Authentication requestingUser = (OAuth2Authentication) securityContext.getUserPrincipal();
        Object principal = requestingUser.getUserAuthentication().getPrincipal();
        User user = null;
        if(principal instanceof User) {
            user = (User)principal;
        } else {
            user = userRepository.findByEmailAddress((String)principal);
        }
        return user;
    }
}

To test this out start up the application:

> ./gradlew tomcatRun


Execute the following curl statement, substituting the access token that was returned from the login curl above

> curl -v -X GET \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer [your token here]" \
  'http://localhost:8080/oauth2-provider/v1.0/me'

To try out some of the other services included in the code see previous posts: