As service architecture grows and encompasses dozens of new services, the question, which service is authorized to run some operations throughout the back-end landscape, still needs to be answered: “Should the product feedback service showing the customer opinion about a product obtain read rights on customer resources? 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 which 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 may 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 an 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.

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 realize user- and- client authorization on protected resources without sharing their credentials with each other. The roles defined in OAuth are:

  1. the resource owner, that is the end-user who owns resource, the resource hosted by a resource server, the third party application, which is
  2. the client, that needs access on resource owner’s protected resources and perform some operations on behalf of the its user and
  3. 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.
  4. 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 server which issues access tokens to the clients after a successful authentication.

Access Token

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.

Authorization Grants

In the OAuth 2.0 world, the mantra is not to share user credentials with 3rd party applications like a mobile app used by a user. The application, the client, might be a native application running as native application in the OS, or some JavaScript application run by a browser. Depending on the degree of the trust between client and user, a client might use the user credentials to obtain an access token. It requires, however, a high degree of trust. Even though the client is allow to see the user credentials in this authorisation grant “Resource Owner Password Credentials”, it is over a short period of time, till it acquires the access token from the authorisation server.

Project structure

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 the client id, optional scope parameter and redirection URI by the client so that the user can authenticate itself. A appropriate real-life example is 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, the client access to the user credentials is not permitted.

There are two more authorization grants, the “implicit” one and the “client credentials”. In the implicit workflow, the user-agent will be redirected to the client, directly, with an access token, instead of with an auth code and without requiring the client to get authenticated with the auth code. This type of authorization we are familiar of from the JavaScript web applications. The authorization grand with “client credentials” requires the client to get authenticate itself with its own credentials. This type of authorisation grant is useful in case the client is the resource owner and defines the scope of access.

Scopes

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

Scopes are optional in workflows and we will revisit scopes later.

Building the Backend

So far, I have covered some fundamental topics of the OAuth 2.0, that we need to understand to build our authorization backend. By the way, if you are interested in digginig into the OAuth 2.0, I strongly encourage you to read the RFC 6749. It is, in my opinion, the most concise document explaining the OAuth 2.0 workflows clearly.

Next step, we are going to start building our backend components like Authorization Server, Resource Server, etc. to implement access control machinery among the rest resources and to grant access the users to their protected resources. The resources are REST resources just giving out some “important” information like “Hello World!”, that we want to protect:

@Controller
@RequestMapping("/hello")
public class HelloResource {

    @Autowired
    private HelloWorkflow workflow;
    
    @Value("${build}")
    private String buildNumber;
    
    @RequestMapping(method = RequestMethod.GET)
    public @ResponseBody
    ResponseEntity handleHello(Principal principal) {
        ResponseEntity v = new ResponseEntity();
        v.msg = workflow.getHelloForWorld("Erhan");
        v.buildNumber = buildNumber;
        return v;
    }
}

The REST resource above handles the requests on resources with the path /hello. The actual message will be returned from the HelloWorld workflow which is injected to the resource by the IoC controller.

Setting up the Development Environment

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:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `accountNonExpired` tinyint(4) NOT NULL,
  `accountNonLocked` tinyint(4) NOT NULL,
  `credentialsNonExpired` tinyint(4) NOT NULL,
  `enabled` tinyint(4) NOT NULL,
  `password` varchar(255) NOT NULL,
  `username` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_r43af9ap4edm43mmtq01oddj6` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

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:

version: '2' ➊
services: ➋
  auth:  ➌
    image: ryos-auth
    depends_on:
    - auth-db
    ports:
    -  "9000:8080"
  auth-db:  ➌
    image: mysql/mysql-server
    environment:
    - MYSQL_DATABASE=ryos_auth
    - MYSQL_USER=ryos
    - MYSQL_PASSWORD=123456
    - MYSQL_ROOT_PASSWORD=123456
    - MYSQL_ROOT_HOST=172.18.0.1
    ports:
    - "3306:3306"

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.

Project structure

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:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>${spring.boot.version}</version>
        <relativePath />
    </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:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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:

@SpringBootApplication(scanBasePackages={"io.ryos"}) 
@EnableJpaRepositories(basePackages={"io.ryos"}) 
@EnableTransactionManagement 
@PropertySource("version.properties") 
public class Application {

    @Qualifier("dataSource") 
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public DataSource dataSource() { 
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/ryos_auth?useUnicode=true&characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("p@ssw0rd");
        return dataSource;
    }
    
    private Properties jpaProperties() { 
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");  // MySQL5InnoDBDialect   MySQLMyISAMDialect
        properties.put("hibernate.show_sql", "true");
        return properties;
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.MYSQL);
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("io.ryos.dao.entity");
        factory.setDataSource(dataSource());
        factory.setJpaProperties(jpaProperties());
        return factory;
    }
    
    public static void main(String[] args) { 
        SpringApplication.run(Application.class, args);
    }
}

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 configure() methods.

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("userRepositoryService")
    private UserDetailsService userDetailsService; 
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.userDetailsService(userDetailsService); 
    }
    
    @Override
    @Bean(name="authenticationManagerBean")
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean(); 
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() 
                .authorizeRequests().antMatchers("/login").permitAll().and() 
                .authorizeRequests().anyRequest().hasRole("USER"); 
    }
}

➊ 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.

Authorization Server

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 @Autowired, the 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.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
    
    @Autowired
    @Qualifier("userRepositoryService")
    private UserDetailsService userDetailsService;
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); 
    }
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                
        clients.inMemory() 
                .withClient("ryosApp") 
                .secret("abc123")
                .scopes("world") 
                .authorizedGrantTypes("password", "refresh_token") 
                .resourceIds("resource");
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.tokenStore(tokenStore()) 
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
    
    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore(); 
    }
}

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.

Resource Server

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.

@Configuration
@EnableResourceServer
public class ResourcesServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("resource");
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/version").authenticated();
    }
}

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.

Wrap-up

We can test the authorization server by making requests, for instance, with cURL and ask for an access token as well as refresh token:

➜ curl ryosApp:abc123@localhost:9000/oauth/token -d username=bagdemir -d password=123456 -d grant_type=password
{"access_token":"93e8733e-022b-4387-abf5-79974323e7f5","token_type":"bearer","refresh_token":"0fbfc23c-5f1d-4c13-afbd-7c2144122ed2","expires_in":43199,"scope":"read write"}%

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:

➜ curl -X GET http://localhost:9000/version
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}%

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:

➜ curl -X GET http://localhost:9000/version -H "Authorization: Bearer 32f3f08e-e124-444b-bfc4-55bd559b44a7"
{"versionInfo":1.0,"buildInfo":"1","releaseDateStr":"12.04.2018"}