A Guide to Access Control for Rest Services with Spring
As service architecture grows and encompasses new services, the question, which service is authorized to run some operations throughout the back-end landscape?”, still needs to be answered: “Does the feedback service, that reveals the customer opinion about a product, really need to obtain read rights on customer database service? Or write rights? Or both?”. In that regard, managing permissions on protected re sources without sharing the user credentials of whom behalf the operation is being executed, among all services becomes important, but definetly, it is not a trivial task.
Today, understanding OAuth 2.0 as so to manage authorization and to control user access on your REST resources is an essential part of Java Enterprise applications. Fortunately, Spring Security - and as a sub-module, the Spring-Security-OAuth, provides a solid framework, that you can easily exert the power thereof and grant access to the services on protected resources while still complying with the OAuth 2.0 specification.
However, the Spring Security might sometimes be challenging to get started, since the learning curve requires some experience with the Spring framework as well as the OAuth 2.0 specification. With this making-of blog article, my goal is to give a high-level overview about OAuth 2.0 concepts with a real-life example of an OAuth-aware back-end incorporating with Spring Boot by melting the theory and the practice in a single pot.
Our objectives in this article will be, first, understanding the fundamental concepts of the OAuth protocol and secondly, step-by-step implementing a backend with its components.
OAuth 2.0 in a Nutshell
OAuth 2.0 is an authorization protocol standardized under RFC 6749 within the IETF as an open standard, of which purpose is to set the rules such that to define user- and- client authorization on protected resources without sharing their credentials with each other. The roles defined in OAuth are:
➊ the resource owner, that is the end-user who owns resource, the resource hosted by a resource server, the third party application, which is ➋ the client, that needs access on resource owner’s protected resources and perform some operations on behalf of the its user and ➌ the authorization server which manages user and client authentication and authorization. The authorization workflow is started by the the client, that is the application requesting access on resource owner’s resources on behalf of him, e.g a mobile app. ➍ The resource server is the server which hosts the protected resources. It accepts the requests with access token and a kind of enforcer of the authorization rules. The authorization server is the one which issues access tokens to the clients after a successful authentication.
Access tokens are credentials issued to clients allowing them access protected resources. They are valid until the expiration time and might be effective in a certain scope associated by the client e.g read-scope, read-write scope, private scope, etc. By issuing access tokens to the clients, we eleminate the need of sharing resource owner’s credentials, so the client applications like mobile apps do not even need to keep user’s credentials.
There is one more token type, that is refresh token. Refresh token is only used to obtain a new access token before the access token expires. If you think about it, it makes also sense. Once you authorize your mobile app, you don’t want to re-authenticate yourself everytime the access token expires. In this case, the client may use the refresh token to obtain a new access token from the authorization server.
On the other hand, the client might be another web service, and in this case the user will be redirected to the authorization server with a client id, an optional scope parameter and a redirection URI so that the user can authenticate itself. An appropriate real-life example would be “Facebook integration” on Twitter. Twitter, the client, needs access on protected resources to post your recent tweets on your Facebook timeline. After a successful authentication, the request will be redirected to the clients redirection URI with an authorization code with which the client can request an access token from authorisation server. In this “authorization code” grant, luckily the client access to the user credentials is not allowed.
Scope defines the boundaries of the authorization. The authorisation server might allow the clients to specify the scope for the access requests with the scope parameter in which scope the access token should be effective. RFC 6749 3.3
Though scopes are optional, they are important in workflows and we will revisit them later.
So far we have covered fundamental topics about OAuth 2.0, that are required to build our authorization and authentication back-end. Next step, we are going to start building the components like Authorization Server, Resource Server, etc. to implement access control machinery. The back-end, I am giving here as example, is to store book reviews, so there will be users who require access to be able to write some reviews as other users have only read only access to view the reviews.
The API’s REST resource which is handling the review requests looks like as follows:
➊ The REST resource above handles GET requests targeting to
/reviews API endpoint. The response value object returned by BookReviewService ➋ of which instance is injected to the resource by the IoC controller. ➌ The resource handler method also recieves an instance of
Principal which contains the security context and expected an HTTP query parameter which is “isbn” number of the book.
The REST resource above will be deployed in a servlet container, which is already provided by Spring Boot framework. The application, we build is a simple Spring Boot Application such that we can start the container either by running Maven plugin’s goals in the command line, that is provided by the Spring Boot framework, or as Java executable by running the main method in an IDE - or java jar command if the application has already been packaged as JAR file. Moreover, the REST service is backed by a MySQL database so that the user credentials can be persisted.
I am going to set up a docker container environment to start the whole back-end on my local computer while developing the components one-by-one. To work with docker container environment will speed up our development and test cycle and later on, once we decide to deploy our back-end, it is much more easier to roll the services out onto a container orchestration platform like Mesos or Kubernetes - or cloud providers’ containerization platforms like AWS Elastic Container Services.
To build our backend, we will start off with a Maven project and inherit our Maven POM from Spring Boot parent, that is very convenient and ensures that the right versions of Spring dependencies will be added to the project. For the sake of ease, I will create a single project which include both the resource and the authorization server in the same application, but in production, you may want to split them into two separate services.
I started the project off from the Maven simple project archetype and the files are organized in classical Maven project structure, i.e we have the Java packages in src/main/java and resources like configurations, etc. in src/main/resources source directories.
I organized the classes in packages as follows in the project:
io.ryos.auth is for Spring Boot application classes and configurations.
io.ryos.dao contains the classes regarding persistence layer, e.g entities, repositories (of Data Access Layer).
io.ryos.data has some value objects like UserProfile as a representation of user file.
io.ryos.resources is for REST resources and the resources source directory to keep classpath files like configurations, properties-files, etc. The project’s POM is located in the root directory along with docker-compose.yml. The POM includes the Spring Boot’s starter parent:
Example - 3 : Parent POM.
We will also need the following dependencies to enable the Spring Framework in the project and some more dependencies mostly pertaining to Java persistence and MySQL driver because our service needs to connect to the MySQL server:
Example - 4 : Project’s POM file.
Now, we start building the Spring Boot application. We first need a class with a static Java main method and some annotations to configure the Spring Boot application. Spring Boot starts your application by calling the main method you provide. Spring Boot applications by default looks for application.yml in the classpath, that you can also find in the project, as well.
Notice that, the first annotation, ➊
@SpringBootApplication, enables Spring Boot application and it takes the attribute, “scanBasePackages”, a list of package names in which Spring framework should search for annotated Spring components and instantiate them. ➋ The
@EnableJpaRepositories is similar to @SpringBootApplication, and it enables JPA framework, that is required to build the persistence layer, in the packages designated by basePackages. ➌ The third one is the
@EnableTransactionManagement which enables annotation-based Java transaction management. And the last annotation, ➍ @PropertySource defines the name of the configuration file in the classpath:
Example - 5 : Application class.
With the dataSource bean configuration ➎ we set up the data source to connect to the database, that is also injected by dependency injection machinery and added as field variable ➏ with @Autowired annotations. Spring makes the data source class-wide available for the bean definations which need data source for themselves. ➐ is a helper method which provides some properties for JPA. ➑ entityManagerFactory method is a Java-based bean configuration which instantiates and configures an EntityManagerFactory, that is the gate to the persistence context where the entities and their life-cycles are managed. Finally, ➒ is the main method to start the Boot application.
The first component, we create is the authorization server. I will create all components in a single Spring Boot project for the sake of ease. But, in a real-life application, you need probably consider to create separate applications for the authorization and resource server.
Before we begin with authorization server and resource server, first, we need to set up web security for the backend application. Fortunately, Spring provides configurer adapter classes need to be inherited while implementing
➊ Note that we inject userDetailsService, the component we inject is used to access the database to read the user data. ➋ The userDetailsServer is passed to the authentication manager so the authentication manager knows how to access the user table. ➌ We create an instance of authentication manager bean. With ➍ we configure the web security and its boundaries thereof. By default, the security rules apply to all requests, but we can also define some matchers to tell the framework on which resources what kind of rules is going to be effective e.g on ➎ and ➏ we permit all request for
/login whereas requiring to have the role “USER” for any request.
The authorization server is where tokens are issued to the clients. Implementing an Authorization Server with Spring, is just to add another Bean configuration to the project. The bean configuration for the authorization server extends
AuthorizationServerConfigurerAdapter and is annotated with
@EnableAuthorizationServer which suffice to enable the authorization server throughout the application. There are two other Spring components get injected via
authenticationManager and the
userDetailsService. The latter is the repository and DAO indicating how Spring framework access the user data in database and the former is authenticationManager.
The bean configuration for the authorization server extends AuthorizationServerConfigurerAdapter and is annotated with
@EnableAuthorizationServer which enables the authorization server throughout the application. There are two other Spring components get injected via
@Autowired, the authenticationManager and the userDetailsService in the
AuthorizationServerConfiguration class. The latter is the instance to the repository and DAO helps us in accessing the user data in database and the former is authenticationManager.
Let’s stick with the
AuthenticationManager here a bit, the interface with the
authenticate() method. The implementation of AuthenticationManager, the
ProviderManagertries the authenticate the request by iterating over the authentication providers. It is technically hooked up to the security servlet filters, before the request reaches resources. The provider is being called to authenticate the request, and if it is successful returns a non-null response, that indicates that the request authorized. If none of the providers returns a non-null back, then we get an AuthorizationException.
In the AuthorizationServerConfiguration we define three overloaded
configure() methods, ➊ taking a AuthorizationServerSecurityConfigurer as parameter, and configures the security rules for the Authorization Server, by permitting all to access token key end-point wheres requiring having athenticated to access the check-token-endpoínt. In the second configure-method, ➋ we set up the client details service, just like the user details service, it is reponsible from providing information about the client. We use here an in-memory details service with a single client ➌
ryos and its secret, ➍ with read and write scopes and grant types ➎ “password” and “refresh_token”. With ➏ we hand the token store, authentication manager, and the user details service over to the authentication server endpoint configurer. ➐ As token store, we use also an in-memory store provided by the framework.
Now, time to set up the resource server. In resource server configuration, we don’t have that much. The configuration bean is annotated with
@EnableResourceServer while extending ResourceServerConfigurerAdapter class, provided by the framework. The bean configuration overrides the configure method and defines the resource id.
In the overloaded configure()- method which takes the HttpSecurity parameter, HttpSecurity instance is configured while requiring the requests having authorized on matching resource “/version” and having authenticated.
We can test the authorization server by making requests, for instance, with cURL and ask for an access token as well as refresh token:
Once we acquired the access token, we now are be able to access protected resources. But first, let me try to call the REST resource with the access token:
As you can see, we may not access the protected resource without providing an access token. In the next example request, we provide the access token in the Authorization header as bearer: