Setting up an OAuth2 Authorization Server and Resource Provider with Spring Boot

March 13th, 2016 by

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.

OAuth2 Flow with Spring Boot in Action

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

Setting up the Spring Boot Project using the Spring Initializr

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

Setting up the Resource Server Project using the Spring Initializr

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
Invalid request with Postman

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=http://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
Obtaining auth token with Postman

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
Accessing resource with auth-token with Postman

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
Refreshing the access token with Postman

Refreshing the access token with Postman

Tutorial Sources

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

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

Resources

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,

Tags: , , , , , , , , , ,

29 Responses to “Setting up an OAuth2 Authorization Server and Resource Provider with Spring Boot”

  1. xuan hoang Says:

    I use postman and POST these options:
    - URL: http://foo:foosecret@localhost:9000/hascode/oauth/token
    or http://localhost:9000/hascode/oauth/token
    - grant_type: password
    - client_id: foo
    - client_secret: abc123
    - username: bar
    - password: barsecret

    It always responses:
    {
    “error”:”unauthorized”,
    “error_description”:”Full authentication is required to access this resource”
    }

    Please help !

  2. tesnik03 Says:

    This one is the best tutorial that i have found for oauth so far!! Thank you!!

  3. Micha Kops Says:

    Thanks, you’re welcome! :)

  4. Sashko Says:

    Thanks for great article!
    I have a question – could we implement this functionality without using Spring Boot and Cloud?

  5. Marcos Says:

    Excellent !! work from Paraguay !

  6. fholisani Says:

    Thanks for this tutorial

    Can you please write the UI login using Angular JS

  7. Daniel Says:

    How I could obtain new token using a refresh token ?

    Thanks!

  8. Micha Kops Says:

    Hi Daniel,

    thanks for reading! I have updated the article including an example how to refresh the oauth access token.

    Cheers

    Micha

  9. Alexey Says:

    Cool! It’s the best tutorial! Thanks!

  10. Marcin Piechota Says:

    I have been digging through StackOverflow and various Spring Tutorials for the last eight hours to solve separation of auth and resource. Finally, I got here. Thank you for the article!

  11. Micha Kops Says:

    Thanks, I’m glad I could be a help for you :)

  12. Ivo Says:

    How can I use datasource here and what tables do I need?

  13. Cesar Says:

    Thanks for your excellent article only I have a comment.

    in the curl instruction you has put

    -d client_id=foo -d client_secret=abc123

    But the secret for food is foodsecret is a little confuse. In order to test this, I change the instruction for
    curl localhost:9000/hascode/oauth/token -d “grant_type=password&username=bar&password=barsecret” -u foo:foosecret

    And it works
    Thanks a lot for your excellent article again

  14. Alex Lopez Says:

    Hi Micha,

    Great post, It has helped my a lot!!! Thank you!!

    Would it be possible to avoid the user and password for token request? is there any other way? I would prefer not to have the password at my mobile App for all the requests.

    Thank you again!

  15. troll Says:

    hi..

    great tutorial!

    can please help,
    im using Postman
    how can i use the token??

  16. hui Says:

    Thanks for great tutorial!

  17. Scott Says:

    Thanks a million! Helped me a lot. Very practical way to start and to work my way from here.

  18. Micha Kops Says:

    Hi Scott, thanks, you’re welcome! :)

  19. Alejandro Tamayo Says:

    Hi Mika,

    Excellent article, very concise and usefull.

    Saludos desde Medellín, Colombia

  20. Micha Kops Says:

    Hola Alejandro, gracias, de nada! :)

  21. Jesse Says:

    When we follow this method by putting our resource values into the properties file. How can we get the access token to be printed out in java? Rather than using command line and enter the command to obtain the access token? Please email me

  22. Atif Says:

    Please tell me how to consume it using postman??

  23. Micha Kops Says:

    Ok, I’ll be adding a postman section :)

  24. Sahil Says:

    Someone, please give me the source code.Me getting while executing.Mail Id: ********
    Thanks in advance.

  25. Micha Kops Says:

    Hi Sahil,

    the source code is listed in this tutorial here #rtm ;) .

    Cheers,

    Micha

  26. harsh sondhi Says:

    Great tutorial!!!! clear concept for grant=password.

  27. Viswa Says:

    Hi,
    This post is helpful for me. But I have small pain area. I build the code as per your inputs. Able to generate the tokens. But i am facing the problem when I try to access a particular end point in the API. I am able to access the API with or withe out token. Also able to access with wrong token..Please help me…

  28. Micha Kops Says:

    Hi Viswa,

    please post a GitHub/Bitbucket link to some project and maybe I can take a look.

  29. Dinesh Says:

    Hey, great article, I did setup the Identity server and it is up and running but when I am trying through postman, it is giving error like
    {
    “error”: “unauthorized”,
    “error_description”: “Full authentication is required to access this resource”
    }

    My request – http://localhost:9000/hascode/oauth/token?grant_type=password&client_id=foo&client_secret=foosecret&redirect_url=http://www.hashcode.com&username=bar&password=barsecret

    Please help

    Thanks,
    Dinesh

Leave a Reply

Please leave these two fields as-is:

Protected by Invisible Defender. Showed 403 to 119,293 bad guys.

Search
Categories