If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

The new Certification Class of REST With Spring is out:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll secure a REST API with OAuth and consume it from a simple AngularJS client.

The application we’re going to build out will consist of four separate modules:

  • Authorization Server
  • Resource Server
  • UI implicit – a front end app using the Implicit Flow
  • UI password – a front end app using the Password Flow

Further reading:

Using JWT with Spring Security OAuth

A guide to using JSON Web Tokens with both symmetric and asymmetric signing in Spring Security OAuth.

Read more

OAuth2.0 and Dynamic Client Registration

Learn how to define clients dynamically with Spring Security and OAuth2.

Read more

2. The Authorization Server

First, let’s start setting up an Authorization Server as a simple Spring Boot application.

2.1. Maven Configuration

We’ll set up the following set of dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>  
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>${oauth.version}</version>
</dependency>

Note that we’re using spring-jdbc and MySQL because we’re going to use a JDBC backed implementation of the token store.

2.2. @EnableAuthorizationServer

Now, let’s start configuring the authorization server responsible for managing access tokens:

@Configuration
@EnableAuthorizationServer
public class AuthServerOAuth2Config
  extends AuthorizationServerConfigurerAdapter {
 
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(
      AuthorizationServerSecurityConfigurer oauthServer) 
      throws Exception {
        oauthServer
          .tokenKeyAccess("permitAll()")
          .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) 
      throws Exception {
        clients.jdbc(dataSource())
          .withClient("sampleClientId")
          .authorizedGrantTypes("implicit")
          .scopes("read")
          .autoApprove(true)
          .and()
          .withClient("clientIdPassword")
          .secret("secret")
          .authorizedGrantTypes(
            "password","authorization_code", "refresh_token")
          .scopes("read");
    }

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

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource());
    }
}

Note that:

  • In order to persist the tokens, we used a JdbcTokenStore
  • We registered a client for the “implicit” grant type
  • We registered another client and authorized the “password“, “authorization_code” and “refresh_token” grant types
  • In order to use the “password” grant type we need to wire in and use the AuthenticationManager bean

2.3. Data Source Configuration

Next, let’s configure our data source to be used by the JdbcTokenStore:

@Value("classpath:schema.sql")
private Resource schemaScript;

@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
    DataSourceInitializer initializer = new DataSourceInitializer();
    initializer.setDataSource(dataSource);
    initializer.setDatabasePopulator(databasePopulator());
    return initializer;
}

private DatabasePopulator databasePopulator() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScript(schemaScript);
    return populator;
}

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

Note that, as we are using JdbcTokenStore we need to initialize database schema, so we used DataSourceInitializer – and the following SQL schema:

drop table if exists oauth_client_details;
create table oauth_client_details (
  client_id VARCHAR(255) PRIMARY KEY,
  resource_ids VARCHAR(255),
  client_secret VARCHAR(255),
  scope VARCHAR(255),
  authorized_grant_types VARCHAR(255),
  web_server_redirect_uri VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(255)
);

drop table if exists oauth_client_token;
create table oauth_client_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255)
);

drop table if exists oauth_access_token;
create table oauth_access_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication_id VARCHAR(255) PRIMARY KEY,
  user_name VARCHAR(255),
  client_id VARCHAR(255),
  authentication LONG VARBINARY,
  refresh_token VARCHAR(255)
);

drop table if exists oauth_refresh_token;
create table oauth_refresh_token (
  token_id VARCHAR(255),
  token LONG VARBINARY,
  authentication LONG VARBINARY
);

drop table if exists oauth_code;
create table oauth_code (
  code VARCHAR(255), authentication LONG VARBINARY
);

drop table if exists oauth_approvals;
create table oauth_approvals (
	userId VARCHAR(255),
	clientId VARCHAR(255),
	scope VARCHAR(255),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);

drop table if exists ClientDetails;
create table ClientDetails (
  appId VARCHAR(255) PRIMARY KEY,
  resourceIds VARCHAR(255),
  appSecret VARCHAR(255),
  scope VARCHAR(255),
  grantTypes VARCHAR(255),
  redirectUrl VARCHAR(255),
  authorities VARCHAR(255),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(255)
);

Note that we don’t necessarily need the explicit DatabasePopulator bean – we could simply use a schema.sql – which Spring Boot makes use of by default.

2.4. Security Configuration

Finally, let’s secure the Authorization Server.

When the client application needs to acquire an Access Token, it will do so after a simple form-login driven auth process:

@Configuration
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
      throws Exception {
        auth.inMemoryAuthentication()
          .withUser("john").password("123").roles("USER");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() 
      throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }
}

A quick note here is that the form login configuration isn’t necessary for the Password flow – only for the Implicit flow – so you may be able to skip it depending on what OAuth2 flow you’re using.

3. The Resource Server

Now, let’s discuss the resource server; this is essentially the REST API which we ultimately want to be able to consume.

3.1. Maven Configuration

Our Resource Server configuration is the same as the previous Authorization Server application configuration.

3.2. Token Store Configuration

Next, we will configure our TokenStore to access the same database that authorization server uses to store access tokens:

@Autowired
private Environment env;

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
    dataSource.setUrl(env.getProperty("jdbc.url"));
    dataSource.setUsername(env.getProperty("jdbc.user"));
    dataSource.setPassword(env.getProperty("jdbc.pass"));
    return dataSource;
}

@Bean
public TokenStore tokenStore() {
    return new JdbcTokenStore(dataSource());
}

Note that, for this simple implementation, we’re sharing the SQL backed token store even though the Authorization and Resource servers are separate applications.

The reason, of course, is that the Resource Server needs to be able to check the validity of the access tokens issued by the Authorization Server.

3.3. Remote Token Service

Instead of using a TokenStore in our Resource Server, we can use RemoteTokeServices:

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl(
      "http://localhost:8080/spring-security-oauth-server/oauth/check_token");
    tokenService.setClientId("fooClientIdPassword");
    tokenService.setClientSecret("secret");
    return tokenService;
}

Note that:

  • This RemoteTokenService will use CheckTokenEndPoint on Authorization Server to validate AccessToken and obtain Authentication object from it.
  • The can be found at AuthorizationServerBaseURL +”/oauth/check_token
  • The Authorization Server can use any TokenStore type [JdbcTokenStore, JwtTokenStore, …] – this won’t affect the RemoteTokenService or Resource server.

3.4. A Sample Controller

Next, let’s implement a simple controller exposing a Foo resource:

@Controller
public class FooController {

    @PreAuthorize("#oauth2.hasScope('read')")
    @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
    @ResponseBody
    public Foo findById(@PathVariable long id) {
        return 
          new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

Note how the client needs the “read” scope to access this Resource.

We also need to enable global method security and configure MethodSecurityExpressionHandler:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig 
  extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }
}

And here’s our basic Foo Resource:

public class Foo {
    private long id;
    private String name;
}

3.5. Web Configuration

Finally, let’s set up a very basic web configuration for the API:

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.web.controller" })
public class ResourceWebConfig extends WebMvcConfigurerAdapter {}

4. Front end – Password Flow

We’re now going to look at a simple front-end AngularJS implementation for the client.

We’re going to be using the OAuth2 Password flow here – which is why this is just a proof of concept, not a production ready application. You’ll notice that the client credentials are exposed to the front end – which is something we’ll address in a future article.

Let’s start with the two simple pages – “index” and “login”; once a user provides their credentials, the front-end JS client uses them to acquire an Access Token from Authorization Server.

4.1. Login Page

Here is our simple login page:

<body ng-app="myApp" ng-controller="mainCtrl">
<h1>Login</h1>
<label>Username</label><input ng-model="data.username"/>
<label>Password</label><input type="password" ng-model="data.password"/>
<a href="#" ng-click="login()">Login</a>
</body>

4.2. Obtain Access Token

Now, let’s see how to obtain our access token:

var app = angular.module('myApp', ["ngResource","ngRoute","ngCookies"]);
app.controller('mainCtrl', 
  function($scope, $resource, $http, $httpParamSerializer, $cookies) {
    
    $scope.data = {
        grant_type:"password", 
        username: "", 
        password: "", 
        client_id: "clientIdPassword"
    };
    $scope.encoded = btoa("clientIdPassword:secret");
    
    $scope.login = function() {   
     	var req = {
            method: 'POST',
            url: "http://localhost:8080/spring-security-oauth-server/oauth/token",
            headers: {
                "Authorization": "Basic " + $scope.encoded,
                "Content-type": "application/x-www-form-urlencoded; charset=utf-8"
            },
            data: $httpParamSerializer($scope.data)
        }
        $http(req).then(function(data){
            $http.defaults.headers.common.Authorization = 
              'Bearer ' + data.data.access_token;
            $cookies.put("access_token", data.data.access_token);
            window.location.href="index";
        });   
   }    
});

Note that:

  • To get an Access Token we send a POST to the “/oauth/token” endpoint
  • We’re using the client credentials and Basic Auth to hit this endpoint
  • We’re then sending the user credentials along with the client id and grant type parameters URL encoded
  • After we obtain the Access Token – we store it in a cookie

The cookie storage is especially important here, because we’re only using the cookie for storage purposes and not to drive the authentication process directly. This helps protect against cross-site request forgery (CSRF) type of attacks and vulnerabilities.

4.3. The Index Page

Here is our simple index page:

<body ng-app="myApp" ng-controller="mainCtrl">
<h1>Foo Details</h1>
<label>ID</label><span>{{foo.id}}</span>
<label>Name</label><span>{{foo.name}}</span>
<a href="#" ng-click="getFoo()">New Foo</a>
</body>

4.4. Authorize Client Requests

As we will need to authorize our requests to the resource using our access token, we will append a simple authorization header with access token:

var isLoginPage = window.location.href.indexOf("login") != -1;
if(isLoginPage){
    if($cookies.get("access_token")){
        window.location.href = "index";
    }
} else{
    if($cookies.get("access_token")){
        $http.defaults.headers.common.Authorization = 
          'Bearer ' + $cookies.get("access_token");
    } else{
        window.location.href = "login";
    }
}

If no cookie is found, the user will be redirected to login page.

5. Front End – Implicit Grant

Now, let’s take a look at our client application that uses the implicit grant.

Our client application is a separate module that tries to access resources server after obtaining an access token from authorization server using implicit grant flow.

5.1. Maven Configuration

Here are pom.xml dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Note: we didn’t need OAuth dependency – as we will handle it using AngularJS directive OAuth-ng which can connect to OAuth2 server with implicit grant flow.

5.2. Web Configuration

And here is our simple web configuration:

@Configuration
@EnableWebMvc
public class UiWebConfig extends WebMvcConfigurerAdapter {
    @Bean
    public static PropertySourcesPlaceholderConfigurer 
      propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Override
    public void configureDefaultServletHandling(
      DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        super.addViewControllers(registry);
        registry.addViewController("/index");
        registry.addViewController("/oauthTemplate");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
          .addResourceLocations("/resources/");
    }
}

5.3. The Home Page

Next, here is our home page:

OAuth-ng directive needs:

  • site: authorization server URL
  • client-id: application client id
  • redirect-uri: the URI to be redirected to after obtaining an access token from the authorization server
  • scope: privilege requested from Authorization Server
  • template: rendered custom HTML template
<body ng-app="myApp" ng-controller="mainCtrl">
    <oauth
      site="http://localhost:8080/spring-security-oauth-server"
      client-id="clientId"
      redirect-uri="http://localhost:8080/spring-security-oauth-ui-implicit/index"
      scope="read"
      template="oauthTemplate">
    </oauth>

<h1>Foo Details</h1>
<label >ID</label><span>{{foo.id}}</span>
<label>Name</label><span>{{foo.name}}</span>
</div>
<a href="#" ng-click="getFoo()">New Foo</a>

<script 
  src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js">
</script>
<script 
  src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js">
</script>
<script 
  src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-route.min.js">
</script>
<script 
  src="https://cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.9/ngStorage.min.js">
</script>
<script th:src="@{/resources/oauth-ng.js}"></script>
</body>

Note how we’re using the OAuth-ng directive to obtain the Access Token.

Also, here’s the simple oauthTemplate.html:

<div>
  <a href="#" ng-show="show=='logged-out'" ng-click="login()">Login</a>
  <a href="#" ng-show="show=='denied'" ng-click="login()">Access denied. Try again.</a>
</div>

5.4. AngularJS App

And here is our AngularJS app:

var app = angular.module('myApp', ["ngResource","ngRoute","oauth"]);
app.config(function($locationProvider) {
  $locationProvider.html5Mode({
      enabled: true,
      requireBase: false
    }).hashPrefix('!');
});

app.controller('mainCtrl', function($scope,$resource,$http) {
    $scope.$on('oauth:login', function(event, token) {
        $http.defaults.headers.common.Authorization= 'Bearer ' + token.access_token;
    });

    $scope.foo = {id:0 , name:"sample foo"};
    $scope.foos = $resource(
      "http://localhost:8080/spring-security-oauth-resource/foos/:fooId", 
      {fooId:'@id'});
    $scope.getFoo = function(){
        $scope.foo = $scope.foos.get({fooId:$scope.foo.id});
    } 
});

Note how, after obtaining the Access Token, we’re using it via the Authorization header whenever we consume protected resources from within the Resource Server.

6. Conclusion

We learned how to authorize our application using OAuth2.

The full implementation of this tutorial can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE CLASSES

Sort by:   newest | oldest | most voted
Felipe Borella
Guest

Hi

How can I run the four modules and test the flow?

Thanks

Eugen Paraschiv
Guest

Hey Felipe – you just need to deploy the modules into a web server together – I’m simply using Eclipse with a Tomcat server for local work. Now – they’re separate apps, so with a bit of configuration they can of course run separately – but yeah, the simplest way to do it is to deploy them together. Cheers,
Eugen.

Felipe Borella
Guest

Thanks! It worked! o/

Eugen Paraschiv
Guest

Sounds good, happy to help. Cheers,
Eugen.

Craig Doremus
Guest

Sorry to barge into the middle of this thread, but the OAuth-ng directive seems to have disappeared from the web. Any other ideas?

Eugen Paraschiv
Guest

No idea Craig – I’m not really keeping a close eye on the front-end ecosystem and AngularJS.
One thing I’d recommend is to reach out to the team directly – I’m sure there’s an official channel for that. Cheers,
Eugen.

Felipe Borella
Guest

Hey Eugen!

I’m trying to run the spring-security-oauth-ui-implicit module.
When I click on the Login Button, I got an error.

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Nov 26 10:20:28 BRST 2015
There was an unexpected error (type=Forbidden, status=403).
Access Denied

Any idea?

Thanks
Felipe

Eugen Paraschiv
Guest

Hey Felipe – I can reproduce the problem – looking into it. Cheers,
Eugen.

Felipe Borella
Guest

I don’t know whats happends. The error persist.
There was an unexpected error (type=Forbidden, status=403).
Access Denied

Thanks

Eugen Paraschiv
Guest

Hey Felipe – I can’t reproduce this one – I’m able to log in and consume the API just fine. If you have the exact steps to reproduce it – go ahead and open an issue on the github project and we’ll track it there. Thanks,
Eugen.

Arthur Carvalho
Guest

Felipe, this error happened to me, you’re probably with the tomcat port other than 8081

Felipe Borella
Guest

Correct! Where are you from? Brazil?

Arthur Carvalho
Guest

Brazil! 🙂

Felipe Borella
Guest

It works now! Thanks

maruf571
Guest

It would be great if you provide some demo data with schema

Eugen Paraschiv
Guest

Well, there is an operation to create a new resource after login, so that’s a quick way to create some data. What other types of data would be help here? Cheers,
Eugen.

Johhny
Guest

Hi Eugen,

What about acces_token refresh? What happens when the acces_token expires, in the password flow scenario?

Eugen Paraschiv
Guest
Hey Johny – so that depends on if the Resource Server is issuing refresh tokens (or not). If not – then you really don’t have any other option than go through the process again and get a new token. That’s of course going to inform your decision regarding the expiration period for these access tokens. But – if you do have refresh tokens – than you’ll use that to get a new access token. That way – access tokens can be very short-lived and it’s only the refresh token that is longer lived. Of course that also leads into things… Read more »
Johnny
Guest

Well, that was the interesting question: how to store refresh tokens in order not to have security issues? It seems that I’ll have to wait until the Advanced API Security is written. Thanks.

Gaurav Dighe
Guest

Hi Eugene,

1. How to integrate User and Role saved in Database.
2. Here you have your HTML in the same project. How to integrate it with login page kept in different AngularJS project? Say my api at localhost:8080 and ui at localhost/login

Eugen Paraschiv
Guest

Hey Gaurav,
1. Have a look at my registration tutorial for a working example – but very quickly, you’d simply use your own UserDetailsService
2. That’s a complex issue, so I won’t get into all of it, but you need to look into the CORS support in Spring to allow your JS to consume the API across origins and then you of course also need to make sure that your front end points to the correct URLs where your API is deployed.
Hope that helps. Cheers,
Eugen.

wpDiscuz