OAuth2 is a frequently used standard for authorization and with Spring Boot it is easy to set up authorization and resource server in no time.

In the following short tutorial I’d like to demonstrate how to set up an OAuth2 authorization server as well as a connected and secured resource server within a few minutes using Java, Maven and Spring Boot.

springboot and oauth2 in action 1024x346
Figure 1. OAuth2 Flow with Spring Boot in Action

Creating the Authorization Server

We’re using the Spring Initializr to create our initial Maven project including some selected dependencies ..

Setting up the Spring Boot Project using the Spring Initializr

spring initializr setup for authorization server 1024x837

This is what our pom.xml looks like (excerpt)..

<project>
  [...]
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.1.RELEASE</version>
    <relativePath/>
    <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Brixton.M4</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project>

The following Java class is all we need to create our authorization server as we’re using an in-memory store with only one oauth client and the server is configured to allow password, auth-code and refresh-token as grant  types.

Our user method returns the Principal User and is used by the resource-server later.

package com.hascode.tutorial;

import java.security.Principal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@RestController
@EnableResourceServer
public class Oauth2AuthorizationServerApplication extends WebMvcConfigurerAdapter {

	public static void main(String[] args) {
		SpringApplication.run(Oauth2AuthorizationServerApplication.class, args);
	}

	@Configuration
	@EnableAuthorizationServer
	protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
		@Autowired
		private AuthenticationManager authenticationManager;

		@Override
		public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
			endpoints.authenticationManager(authenticationManager);
		}

		@Override
		public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
			clients.inMemory().withClient("foo").secret("foosecret")
					.authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid");
		}
	}

	@RequestMapping("/user")
	public Principal user(Principal user) {
		return user;
	}

}

In our application.properties we’re adding some configuration for the server port, the credentials used for the basic-auth and our application’s context path:

server.port=9000
security.user.name=bar
security.user.password=barsecret
server.contextPath=/hascode

Minimalistic Spring Boot 1.3 Setup

The following, minimalistic setup is possible in Spring Boot 1.3 if we needed only one client (for demonstration purpose etc.), a detailed description has been documented here in the Spring Blog.

Our application controller would now look like this one:

package com.hascode.tutorial;

import java.security.Principal;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
@RestController
@EnableResourceServer
@EnableAuthorizationServer
public class Oauth2AuthorizationServerApplication extends WebMvcConfigurerAdapter {

	public static void main(String[] args) {
		SpringApplication.run(Oauth2AuthorizationServerApplication.class, args);
	}

	@RequestMapping("/user")
	public Principal user(Principal user) {
		return user;
	}

}

And our modified application.properties would look similar to this one:

server.port=9000
security.user.name=bar
security.user.password=barsecret
server.contextPath=/hascode
security.oauth2.client.clientId=foo
security.oauth2.client.clientSecret=foosecret
security.oauth2.client.authorized-grant-types=authorization_code,refresh_token,password
security.oauth2.client.scope=openid

Creating the Resource Provider

Again we’re using the Spring Initializr to create our initial project here..

spring initializr resource provider 1024x837
Figure 2. Setting up the Resource Server Project using the Spring Initializr

This is an excerpt from our pom.xml:

<project>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.1.RELEASE</version>
    <relativePath/>
    <!-- lookup parent from repository -->
  </parent>

  [...]

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Brixton.M4</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <repositories>
   [..]
  </repositories>
</project>

And this is our resource, calling the secured API simply return a success message an a generated UUID.

package com.hascode.tutorial;

import java.util.UUID;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@EnableResourceServer
public class SampleResourceApplication {
	public static void main(String[] args) {
		SpringApplication.run(SampleResourceApplication.class, args);
	}

	@RequestMapping("/")
	public String securedCall() {
		return "success (id: " + UUID.randomUUID().toString().toUpperCase() + ")";
	}
}

In our application.properties we’re telling our resource server

server.port=9001
server.contextPath=/resource
security.oauth2.resource.userInfoUri: http://localhost:9000/hascode/user

When using Spring Boot <1.3, the last property is named spring.oauth2.resource.userInfoUri!!

Running the Applications

We may run both applications by invoking the main class or using Maven in the command-line with mvn spring-boot:run:

$ mvn spring-boot:run
[..]
2017-05-01 14:45:10.643  INFO 13279 --- [           main] h.t.Oauth2AuthorizationServerApplication : Starting Oauth2AuthorizationServerApplication on styx with PID 13279 (/data/project/spring-oath2-sample/identity-server/target/classes started by soma in /data/project/spring-oath2-sample/identity-server)
[..]
2017-05-01 14:45:13.233  INFO 13279 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 9000 (http)
[..]
2017-05-01 14:45:14.217  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/check_token]}" onto public java.util.Map<java.lang.String, ?> org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken(java.lang.String)
2017-05-01 14:45:14.223  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/authorize]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.String>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
2017-05-01 14:45:14.224  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/authorize],methods=[POST],params=[user_oauth_approval]}" onto public org.springframework.web.servlet.View org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.approveOrDeny(java.util.Map<java.lang.String, java.lang.String>,java.util.Map<java.lang.String, ?>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
2017-05-01 14:45:14.224  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/confirm_access]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint.getAccessConfirmation(java.util.Map<java.lang.String, java.lang.Object>,javax.servlet.http.HttpServletRequest) throws java.lang.Exception
2017-05-01 14:45:14.224  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/error]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelErrorEndpoint.handleError(javax.servlet.http.HttpServletRequest)
2017-05-01 14:45:14.226  INFO 13279 --- [ost-startStop-1] .s.o.p.e.FrameworkEndpointHandlerMapping : Mapped "{[/oauth/token],methods=[POST]}" onto public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException
[..]
2017-05-01 14:45:15.139  INFO 13279 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9000 (http)
2017-05-01 14:45:15.141  INFO 13279 --- [           main] h.t.Oauth2AuthorizationServerApplication : Started Oauth2AuthorizationServerApplication in 5.744 seconds (JVM running for 8.358)

OAuth2 Workflow in Action

Assuming we have both server instances up and running, we may now test if accessing our secured resource using OAuth2 is working.

Testing for Authentication Error

Accessing the resource in a direct GET request should not be possible and yield an error message like this one:

curl

$ curl http://localhost:9001/resource/
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

Postman

postman 01 invalid request
Figure 3. Invalid request with Postman

Obtaining the Access Token

Now we’re obtaining our access token from the authorization server:

curl

$ curl -XPOST -k foo:foosecret@localhost:9000/hascode/oauth/token \
   -d grant_type=password -d client_id=foo -d client_secret=abc123 \
   -d redirect_uri=https://www.hascode.com -d username=bar -d password=barsecret

{"access_token":"dec6c15a-137f-475a-aa02-530c23943f91","token_type":"bearer","refresh_token":"19b44e18-a25f-427c-9884-ebe6dcec1b96","expires_in":43192,"scope":"openid"}

The response contains our access token, its expiration date, the refresh token (for refresh after expiration), the token type and its scope.

Postman

postman 02 obtaining oauth token
Figure 4. Obtaining auth token with Postman

Accessing the Resource

With the obtained access token, we may now access the secured resource like this:

curl

$ TOKEN=dec6c15a-137f-475a-aa02-530c23943f91
$ curl -H "Authorization: Bearer $TOKEN" http://localhost:9001/resource/
success (id: 27DCEF5E-AF11-4355-88C5-150F804563D0)

Postman

postman 03 accessing resource with bearer token
Figure 5. Accessing resource with auth-token with Postman

Refreshing the Access Token

May may refresh our access token using the refresh token received in our first  authorization request:

curl

$ curl -v --data "grant_type=refresh_token&client_id=foo&refresh_token=19b44e18-a25f-427c-9884-ebe6dcec1b96" -k foo:foosecret@localhost:9000/hascode/oauth/token
{"access_token":"12f5c219-1e8b-45b6-be42-304a6833a3b1","token_type":"bearer","refresh_token":"19b44e18-a25f-427c-9884-ebe6dcec1b96","expires_in":43199,"scope":"openid"}%

Postman

postman 04 refreshing access token
Figure 6. Refreshing the access token with Postman

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/spring-oauth2-example.git

Article Updates

  • 2016-09-14: Added an example for refreshing the access token.

  • 2017-05-01: Added section about running the application from the command-line.

  • 2017-05-01: Postman examples and screen-shots added,