Setting up an OAuth2 Authorization Server and Resource Provider with Spring Boot
March 13th, 2016 by Micha KopsOAuth2 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.
Contents
Creating the Authorization Server
We’re using the Spring Initializr to create our initial Maven project including some selected dependencies ..
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..
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
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
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
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
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
- Spring Boot
- RFC 6749: OAuth2 Specification
- Spring Initializr
- Spring Blog: Migration OAuth2 Apps from Spring Boot 1.2 to 1.3
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: authentication, boot, identity, oauth, oauth2, postman, protocol, security, Snippet, spring, standard
May 29th, 2016 at 5:21 pm
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 !
June 16th, 2016 at 5:20 pm
This one is the best tutorial that i have found for oauth so far!! Thank you!!
June 16th, 2016 at 8:00 pm
Thanks, you’re welcome! :)
June 24th, 2016 at 10:33 am
Thanks for great article!
I have a question – could we implement this functionality without using Spring Boot and Cloud?
July 19th, 2016 at 12:56 am
Excellent !! work from Paraguay !
August 2nd, 2016 at 8:33 am
Thanks for this tutorial
Can you please write the UI login using Angular JS
August 25th, 2016 at 1:59 pm
How I could obtain new token using a refresh token ?
Thanks!
September 14th, 2016 at 9:36 am
Hi Daniel,
thanks for reading! I have updated the article including an example how to refresh the oauth access token.
Cheers
Micha
September 28th, 2016 at 8:23 am
Cool! It’s the best tutorial! Thanks!
October 6th, 2016 at 3:13 pm
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!
October 7th, 2016 at 4:09 am
Thanks, I’m glad I could be a help for you :)
October 18th, 2016 at 11:19 am
How can I use datasource here and what tables do I need?
November 4th, 2016 at 9:44 pm
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
November 16th, 2016 at 9:26 pm
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!
November 24th, 2016 at 11:10 am
hi..
great tutorial!
can please help,
im using Postman
how can i use the token??
January 5th, 2017 at 11:20 am
Thanks for great tutorial!
January 21st, 2017 at 5:16 pm
Thanks a million! Helped me a lot. Very practical way to start and to work my way from here.
January 23rd, 2017 at 6:30 am
Hi Scott, thanks, you’re welcome! :)
January 26th, 2017 at 3:37 pm
Hi Mika,
Excellent article, very concise and usefull.
Saludos desde Medellín, Colombia
January 27th, 2017 at 5:21 am
Hola Alejandro, gracias, de nada! :)
March 27th, 2017 at 9:06 pm
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
April 24th, 2017 at 10:42 pm
Please tell me how to consume it using postman??
April 26th, 2017 at 10:46 pm
Ok, I’ll be adding a postman section :)
May 12th, 2017 at 10:07 pm
Someone, please give me the source code.Me getting while executing.Mail Id: ********
Thanks in advance.
May 15th, 2017 at 9:19 am
Hi Sahil,
the source code is listed in this tutorial here #rtm ;).
Cheers,
Micha
June 19th, 2017 at 9:59 pm
Great tutorial!!!! clear concept for grant=password.
July 19th, 2017 at 8:05 am
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…
July 19th, 2017 at 9:47 pm
Hi Viswa,
please post a GitHub/Bitbucket link to some project and maybe I can take a look.
August 13th, 2017 at 1:18 am
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
October 24th, 2017 at 12:53 pm
HI,
Nice tutorial.
Can u please tell me how can we get the
security.user.name
security.user.password
from db because these are hard coded
May 14th, 2018 at 8:55 am
Hi, How do i achieve role based authorization in this. Its validating users only, but it is not applying roles which is specified in authorization server.
June 19th, 2018 at 4:17 pm
can you help in upgrading it to spring boot 2