Spring Boot + OAuth2 + Securing REST API + MySQL

In this blog, we will learn about OAuth2 and how we can secure our REST API using OAuth2 with Spring JPA in the Spring Boot application. Spring boot oauth2 rest API example. #spring boot + oauth2 + mysql

What is OAuth2? Why it is needed?

OAuth2 provides authorization flows for web and desktop applications, and mobile devices.
OAuth2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, such as Facebook, GitHub, and DigitalOcean.

How can the provided sample apps be easily extended and re-configured for more specific use cases with minimal effort?

The sample applications developed using Spring Boot and Spring Security demonstrate versatility and ease of enhancement. These apps, principally leveraging OAuth 2.0 for authentication, can be customized for specific requirements primarily through simple changes to their configuration files. Such adjustments enable the apps to adapt to different use cases without the need for extensive coding or structural changes. It’s important to ensure that when these applications are deployed, new client credentials are obtained for the respective host addresses from services like GitHub. Additionally, it’s crucial to securely manage these credentials, avoiding their exposure in source control systems.

What is the purpose of using WebClient to access the GitHub API in the context of OAuth2 authentication?

The use of WebClient in the context of OAuth2 authentication when accessing the GitHub API is aimed at effectively verifying the authentication details of a user. Essentially, WebClient facilitates the process by which the application communicates with the GitHub API to confirm a user’s membership in a specific organization. Through this process, WebClient sends requests to retrieve data regarding the user’s affiliated GitHub organizations. If the data shows that the user does not belong to the designated organization, the application will deny access, typically through a 401 unauthorized error. This method strengthens security by ensuring that only members of certain approved organizations are granted access.

How can the authentication rule be extended to reject users not belonging to a specific organization using the GitHub API?

To extend the authentication rule in a Spring Boot application to reject users who do not belong to a specific organization on GitHub, you can define a custom `OAuth2UserService`. This service is responsible for loading and examining the user’s organization membership during the authentication process. Here’s an outline of how you can implement this:
1. **Create a Bean of `OAuth2UserService`:** Start by defining a new bean that implements `OAuth2UserService`. This will intercept the OAuth2 authentication process to add custom verification logic.
2. **Delegate Default User Service:** Inside the service, instantiate `DefaultOAuth2UserService` which is the standard service for user details loading. Use this service to delegate the loading of the OAuth2 user details.
3. **Identify Github Users:** After loading the user details, check if the authentication request comes from GitHub by examining `getClientRegistration().getRegistrationId()` of the `OAuth2UserRequest`. Proceed with further checks only if it matches “github“.
4. **Extract Organization URL:** Get the URL pointing to the user’s organizations from the OAuth2 user’s attributes (e.g., `user.getAttribute(“organizations_url”)`).
5. **Call GitHub API to Fetch Organizations:** Use `WebClient` to send a GET request to the specified organization URL. Attach the necessary OAuth2 credentials to the request. Deserialize the response into a list of organization maps.
6. **Check Organization Membership:** Analyze the response to determine if the user belongs to the required organization, such as “spring-projects”. This can be done by streaming through the list of organizations and checking for a matching ‘login’ attribute.
7. **Handle Unauthorized Users:** If the user is not found in the required organization, throw an `OAuth2AuthenticationException`. This will reject the login attempt, sending an appropriate error response back to the user.
8. **Return the Authenticated User:** If all conditions are satisfied (correct GitHub user and correct organization membership), return the authenticated `OAuth2User` object, completing the custom authentication process.
By following these steps, your Spring Boot application will only authenticate users who are part of a specified organization on GitHub, effectively using the GitHub API to enforce organization-based access control in your application. This implementation relies heavily on Spring Security’s OAuth2 client and its given capabilities to customize user authentication flows involving external systems like GitHub.

How can a 401 response be generated in the server when a user fails to authenticate with GitHub?

To generate a 401 response in a server when a user fails to authenticate with GitHub using Spring Security, follow this approach:

1. **Leverage Spring Security’s built-in mechanisms**: Spring Security automatically produces a 401 Unauthorized response whenever an authentication attempt fails, such as when the user rejects a token grant from GitHub. This default behavior handles basic authentication failures.

2. **Customize the authentication process**: For more specific scenarios, such as restricting access to users from specific GitHub organizations, extend the authentication process using Spring Boot’s customization features. Implement this by defining a custom `OAuth2UserService`.

3. **Define a custom `OAuth2UserService` Bean**: Create a Spring Bean that returns a custom `OAuth2UserService`. This service should extend the behavior of the default `OAuth2UserService` provided by Spring Security.

– Use the `DefaultOAuth2UserService` to fetch user details. – Extract the `organizations_url` from the user’s attributes to query GitHub for the user’s organizations. – Call the GitHub API to retrieve a list of organizations the user belongs to, using a `WebClient` instance configured with the user’s OAuth2 credentials.

4. **Check organization membership**: In the custom `OAuth2UserService`, after retrieving the organizations, check if the user is a member of the required organization (e.g., “spring-projects”). If the user is not found within the specified organization:

– Throw an `OAuth2AuthenticationException` with an appropriate error code and description, such as `invalid_token`. – Spring Security catches this exception and converts it into a 401 Unauthorized response.

5. **Configure WebClient and HTTP Client**: Ensure the `WebClient` used for calling the GitHub API is properly configured to include the necessary credentials and headers to authenticate the request.

By following these steps, the server can effectively enforce organizational access control via GitHub authentication, generating a 401 response when the user fails to meet the specified criteria. This method not only secures the application but also allows for flexibility by adjusting conditions under which a user is considered authenticated.

How can an error message be captured when authentication fails in a Spring Boot app?

To capture an error message when authentication fails in a Spring Boot application, you need to implement a custom authentication failure handling mechanism. Here is a step-by-step approach:

1. **Configure an Authentication Failure Handler**: In your Spring Security configuration, use the `HttpSecurity` object to set up an `AuthenticationFailureHandler`. This handler will be invoked whenever the authentication process fails.

Start by adding to your existing security configuration a failure handler within the `oauth2Login` method. In the lambda function provided to `failureHandler`, use the request session to store the exception message. This involves obtaining the exception message and setting it as a session attribute.

Example: “`java protected void configure(HttpSecurity http) throws Exception { http .oauth2Login(o -> o .failureHandler((request, response, exception) -> { request.getSession().setAttribute(“error.message”, exception.getMessage()); // Optionally forward to a default failure handler // handler.onAuthenticationFailure(request, response, exception); }) ); } “`

2. **Create an Error Controller**: After configuring the failure handler, create a controller to display or handle the error. Define a method mapped to `/error` that retrieves the error message from the session, which was set by the failure handler. Once fetched, ensure to clear the message from the session to prevent it from being re-displayed on subsequent requests.

Example: “`java @GetMapping(“/error”) public String error(HttpServletRequest request) { String message = (String) request.getSession().getAttribute(“error.message”); request.getSession().removeAttribute(“error.message”); return message; } “`

Implementing the above two steps allows your application to effectively capture and display an error message when the authentication fails. This setup also replaces the default error handling, giving you more control over the error management and user feedback in your Spring Boot application.

What are OAuth2 Roles

OAuth defines the four main roles

  • Resource Owner
  • Client
  • Resource Server
  • Authorization Server

Resource Owner: User – The resource owner is the user who authorizes an application to access their account

Resource / Authorization Server – The resource server hosts the protected user accounts, and the authorization server verifies the identity of the user then issues access tokens to the application.

Client: Application – The client is the application that wants to access the user’s account. The user must allow it before it may do so, and the API must validate the authorization.

OAuth2 High-Level Workflow

OAuth2 High Level Workflow, spring boot oauth2 mysql - spring boot oauth2 rest api example

OAuth2 Implicit Workflow

OAuth2 Implicit Workflow - spring boot oauth2 rest api example

Spring Boot: Application Structure

In this example, we are using the following technology stack

  • Spring Boot 2.4.4
  • Java 8
  • MySql
Application structure for a Spring Boot application
package com.the.basic.tech.info;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

Spring Boot: Implement Oauth2 Authorization Server

OAuth2AuthorizationServerConfig class extends AuthorizationServerConfigurerAdapter and is responsible for generating tokens specific to a client.
Suppose, if a user wants to log in to www.xyz.com via Facebook then Facebook Auth Server will be generating tokens for Devglan. In this case, Devglan becomes the client which will be requesting for authorization code on behalf of the user from Facebook – the authorization server. Following is a similar implementation that Facebook will be using.
@EnableAuthorizationServer:
Enables an authorization server. AuthorizationServerEndpointsConfigurer defines the authorization and token endpoints and the token services.

package com.the.basic.tech.info.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.configurers.AuthorizationServerSecurityConfigurer;

@SuppressWarnings("deprecation")
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${user.oauth.clientId}")
    private String clientID;

    @Value("${user.oauth.clientSecret}")
    private String clientSecret;

    @Value("${user.oauth.redirectUris}")
    private String redirectURLs;

    @Value("${user.oauth.accessTokenValidity}")
    private int accessTokenValidity;

    @Value("${user.oauth.refreshTokenValidity}")
    private int refreshTokenValidity;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

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

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
            .inMemory()
            .withClient(clientID)
            .secret(passwordEncoder.encode(clientSecret))
            .authorizedGrantTypes("password", "authorization_code", "refresh_token")
            .scopes("user_info")
            .authorities("READ_ONLY_CLIENT")
            .redirectUris(redirectURLs)
            .accessTokenValiditySeconds(accessTokenValidity)
            .refreshTokenValiditySeconds(refreshTokenValidity);
    }
}

Spring Security Configuration

Below Spring configuration class enables and configures an OAuth authorization server.

package com.the.basic.tech.info.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@SuppressWarnings("deprecation")
@Configuration
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/").permitAll();
    }
}
package com.the.basic.tech.info.config;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
@Order(1)
public class OauthSecurityConfiguration extends WebSecurityConfigurerAdapter {
	
	@Resource(name = "userService")
    private UserDetailsService userDetailsService;

    @Value("${user.oauth.user.username}")
    private String username;

    @Value("${user.oauth.user.password}")
    private String password;

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser(username).password(passwordEncoder().encode(password)).roles("ADMIN");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

Application Properties (application.yaml)

# DB Configuration
spring:
 datasource:
  url: jdbc:mysql://localhost:3306/bootdb
  username: root
  password: admin
 jpa: 
  hibernate.ddl-auto: update
  show-sql: true
 user.datasource.driver-class-name: com.mysql.jdbc.Driver
user.oauth.clientId: devglan-client
user.oauth.clientSecret: devglan-secret
user.oauth.redirectUris: http://localhost:8081/login
user.oauth.user.username: Alex123
user.oauth.user.password: password

user.oauth.accessTokenValidity: 3600
user.oauth.refreshTokenValidity: 21600
security.oauth2.resource.filter-order: 3

Maven File (pom.xm)

Maven file (pom.xml) would be like as below

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>spring-boot-security-oauth2</artifactId>
	<version>1.0-SNAPSHOT</version>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.4</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</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>
			<version>2.4.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>javax.persistence</groupId>
			<artifactId>javax.persistence-api</artifactId>
		</dependency>
	</dependencies>

	<properties>
		<java.version>1.8</java.version>
	</properties>


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

Securing Resources (Rest API) – UserController

package com.the.basic.tech.info.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.the.basic.tech.info.model.User;
import com.the.basic.tech.info.service.UserService;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value="/user", method = RequestMethod.GET)
    public List<User> listUser(){
        return userService.findAll();
    }

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public User create(@RequestBody User user){
        return userService.save(user);
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable(value = "id") Long id){
        userService.delete(id);
        return "success";
    }

}

Application Model and Dao Layer (Spring JPA)

package com.the.basic.tech.info.model;

import javax.persistence.*;

import javax.persistence.Id;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private long id;
    @Column
    private String username;
    @Column
    @JsonIgnore
    private String password;
    @Column
    private long salary;
    @Column
    private int age;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public long getSalary() {
        return salary;
    }

    public void setSalary(long salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
package com.the.basic.tech.info.dao;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.the.basic.tech.info.model.User;

@Repository
public interface UserDao extends CrudRepository<User, Long> {
    User findByUsername(String username);
}

Application Service Layer

package com.the.basic.tech.info.service;

import java.util.List;

import com.the.basic.tech.info.model.User;

public interface UserService {

    User save(User user);
    List<User> findAll();
    void delete(long id);
}
package com.the.basic.tech.info.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.the.basic.tech.info.dao.UserDao;
import com.the.basic.tech.info.model.User;
import com.the.basic.tech.info.service.UserService;


@Service(value = "userService")
public class UserServiceImpl implements UserDetailsService, UserService {
	
	@Autowired
	private UserDao userDao;

	public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
		User user = userDao.findByUsername(userId);
		if(user == null){
			throw new UsernameNotFoundException("Invalid username or password.");
		}
		return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority());
	}

	private List<SimpleGrantedAuthority> getAuthority() {
		return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
	}

	public List<User> findAll() {
		List<User> list = new ArrayList<>();
		userDao.findAll().iterator().forEachRemaining(list::add);
		return list;
	}

	@Override
	public void delete(long id) {
		userDao.deleteById(id);
	}

	@Override
    public User save(User user) {
        return userDao.save(user);
    }
}

Spring Boot: Application Run and Testing

To run the application mvn spring-boot:run command needs to be executed.

2021-06-05 14:56:27.772  INFO 18240 --- [           main] com.the.basic.tech.info.Application      : No active profile set, falling back to default profiles: default
2021-06-05 14:56:28.467  INFO 18240 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-06-05 14:56:28.536  INFO 18240 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 53 ms. Found 1 JPA repository interfaces.
2021-06-05 14:56:29.809  INFO 18240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-06-05 14:56:29.809  INFO 18240 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-06-05 14:56:29.809  INFO 18240 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-06-05 14:56:29.971  INFO 18240 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-06-05 14:56:29.971  INFO 18240 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2121 ms
2021-06-05 14:56:30.157  INFO 18240 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-06-05 14:56:30.210  INFO 18240 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.29.Final
2021-06-05 14:56:30.326  INFO 18240 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-06-05 14:56:30.426  INFO 18240 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-06-05 14:56:30.758  INFO 18240 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-06-05 14:56:30.781  INFO 18240 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2021-06-05 14:56:31.429  INFO 18240 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-06-05 14:56:31.445  INFO 18240 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-06-05 14:56:31.715  WARN 18240 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-06-05 14:56:32.147  INFO 18240 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-06-05 14:56:32.666  INFO 18240 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Or [Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']] with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@c11332b, org.springframework.security.web.context.SecurityContextPersistenceFilter@26a45089, org.springframework.security.web.header.HeaderWriterFilter@3a88f6fb, org.springframework.security.web.authentication.logout.LogoutFilter@c318864, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@45bd4753, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5ab5924c, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@191f4d65, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@7e5e6573, org.springframework.security.web.session.SessionManagementFilter@844e66d, org.springframework.security.web.access.ExceptionTranslationFilter@74d22ddd, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@520a95ff]
2021-06-05 14:56:32.686  INFO 18240 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure Ant [pattern='/**'] with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4ecd8ab1, org.springframework.security.web.context.SecurityContextPersistenceFilter@606d6d2c, org.springframework.security.web.header.HeaderWriterFilter@e3ed455, org.springframework.security.web.csrf.CsrfFilter@12c76d6e, org.springframework.security.web.authentication.logout.LogoutFilter@697a92af, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@69ce14e6, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@2ab8589a, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@57bdceaa, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2e3cd732, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@56d822dc, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@52909a97, org.springframework.security.web.session.SessionManagementFilter@27bb74e1, org.springframework.security.web.access.ExceptionTranslationFilter@7956f93a, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4c65d8e3]
2021-06-05 14:56:32.749  INFO 18240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-06-05 14:56:32.764  INFO 18240 --- [           main] com.the.basic.tech.info.Application      : Started Application in 5.779 seconds (JVM running for 6.467)
2021-06-05 14:56:41.146  INFO 18240 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-06-05 14:56:41.146  INFO 18240 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-06-05 14:56:41.152  INFO 18240 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms

How to Get Authorization Grant Code

http://localhost:8080/oauth/authorize?client_id=devglan-client&response_type=code&scope=user_info

This URL will redirect to the login page. Once the customer provides the login information, the system redirects to the grant access page. The page gives an option to the customer to approve or reject the request or to provide certain access to the third-party applications.

Securing Rest API - Spring Boot

Enter the credentials as Alex123/password

Login screen - Secure Rest api - spring boot

OAuth Approval page will be opened

oAuth Approval in Spring Boot

Once we approve the request, it will redirect to an URL (check the user.oauth.redirectUris property). This redirect URL will also contain a code as part of the query string (http://localhost:8081/login?code=5BsHRS). This code is the authorization code for the third-party application.

Generate Auth Token + Get Access Token

In the header, we have username and password as Alex123 and password respectively as Authorization header. As per Oauth2 specification, the Access token request should use application/x-www-form-urlencoded. Following is the setup.

Auth Token - Spring Boot

Once you make the request you will get the following result. It has an access token as well as a refresh token.

postman screen - Secure Rest API - Spring Boot

Accessing Resource Without Token

Accessing Resource Without Token - Spring Boot

Accessing Resource With Token

Accessing Resource With Token - Spring Boot

Using refresh_token to refresh the token

Usually, the token expiry time is significantly less in the case of oAuth2 and you can use the following API to refresh the token once it is expired.

Using refresh_token to refresh the token - Spring Boot

Download Source Code (Attached)

Thank you for your time. Keep learning 🙂