Figure - 1: OAuth 2.0 Workflow.
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 the OAuth 2.0, that are required to build our authorization backend. Next step we are going to start building our backend components like the Authorization Server, the Resource Server, etc. to implement access control machinery among the rest resources. The resources mentioned are REST resources just giving out some “important” information like “Hello World!”, that we want to protect:
➊ The REST resource above handles GET requests targeting to
/hello API endpoint. The response message “Hello World” along with the build number will be returned from the workflow ➋ which is injected to the resource by the IoC controller. ➌ The resource handler method also recieves an instance of
Principal which contains the security context.
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.
The project defines some database dependencies, too, in addition to these which are required for the OAuth implementation. The idea is basically keeping the user data in a database table. Alternatively, we could store it in a in-memory database, that is fortunately provided by the Spring Framework for testing purpose. Nevertheless, to converge my example to a real-life situation I will use a database and store the user credentials in an SQL database. Thus, I will need an SQL schema for that. For this purpose, I will use a MySQL database with the following schema:
Example - 1 : SQL Schema
Once we succeed in setting up the database and get it up and running, apply it with your favourite mySQL client to the database and create the table for our users.
As I mentioned before, I will dockerize the components to leverage the advantages of docker containers ecosystem. Well, the problem is once we have multiple components and docker containers respectively, managing them like to start/stop all these containers, or passing network configurations to docker for every single docker command might become tedious. To overcome this cumbersome work, I will put Docker Compose in use, to set up my docker environment with a single YAML configuration file:
Example - 2 : Docker compose configuration.
➊ The first line of the docker-compose.yml is the version, which is a fix number indicating which version our configuration file has. ➋ In services section, we define two services, our authorization server and the database. ➌ In each component configuration with image section we tell docker-compose which image we want to use, e.g for auth service we need a docker image called ryos-auth and it depends on auth-db componen i.e as services are starting, the database must be available and docker-compose takes care for that.
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.
Figure - 2 : Project’s structure
The Figure 2 demonstrates the project structure we will work on throughout this article. I organized the classes in packages as follows:
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, we use is a marker for Spring Boot application which takes an attribute “scanBasePackages”, the package names in which Spring framework should search for Spring based components and initialize them. ➋ The
@EnableJpaRepositories is similar to @SpringBootApplication, and it enables JPA repositories which is persistence layer in our application 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: