DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Spring Security Oauth2: Google Login
  • Implementing Your Own Spring Boot Oauth2 Authorization Server

Trending

  • Agentic AI for Automated Application Security and Vulnerability Management
  • Kullback–Leibler Divergence: Theory, Applications, and Implications
  • Simplifying Multi-LLM Integration With KubeMQ
  • Optimizing Integration Workflows With Spark Structured Streaming and Cloud Services
  1. DZone
  2. Coding
  3. Frameworks
  4. How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood

How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood

This post continues Part 1 and demonstrates how the Spring Boot OAuth authentication server processes a token request internally.

By 
Alexander Eleseev user avatar
Alexander Eleseev
DZone Core CORE ·
Sep. 14, 21 · Analysis
Likes (2)
Comment
Save
Tweet
Share
6.6K Views

Join the DZone community and get the full member experience.

Join For Free

Spring Security OAuth server becomes deprecated. However, its replacement is still in development. So, I think it is worthwhile to inspect how the current OAuth server works under the hood. Firstly, this knowledge helps us to quickly figure out how the new server implements the same functionality. Secondly, it will be interesting to compare and contrast the current and the new OAuth servers. 

We build the security component of a distributed Spring Cloud system. The service uses JWT mechanics. The tokens are signed with a private key kept on the server; all other services verify the tokens with the available public key.  

In this post I, demonstrate how the authorization server, configured in Part 1, builds the filter chain and operates the token endpoint for the workflow of Part 1:

The Workflow of Our Authorization Server.

Fig 1. The workflow of our authorization server.

First, a token request passes the Filter Chain, where the request's client:secret pair is verified. Then the request comes to the Token Endpoint. Finally, the request is fed to the Token Granters, where the request's username:password or access token, or refresh token are verified, to produce either a JWT or an access token. Check the code for details. Before we dive into it, let's recall a Spring bean lifecycle.

Spring Bean Lifecycle

Spring Bean Lifecycle

Spring bean lifecycle. Adopted from GeekForGeeks.

Spring Boot' SpringApplication.run(...), after a lot of work to configure the environment and to create bean definitions, instantiates Spring beans. Then, bean dependencies are injected (@Autowire). Next, custom init() methods (annotated with @PostConstruct) are called. After this, the bean's custom utility methods are called. For our system I split this process into "Init" - bean instantiation phase and "Main" - dependency injection and custom init() method calls.

This bean creation process seems straightforward. However, it is not so if beans circularly depend on each other. Later in this post, we will see such a situation. But before that, let me explain my notation.

Notation

In this post, I use somewhat non-standard notation.

Notation For This Post

Notation for this post.

Basically, it is the usual Collaboration diagram notation with some pseudo-code embedded. Other notation elements are clarified on their corresponding figures. Let's see the workflow under the hood.

Step 1. Beans Initialized

At first, our @Configuration - annotated beans are created:

The Bean init Stage in the Authorization Server of Our System

Fig 2. The bean init stage in the authorization server of our system. "I" stands for "bean instantiation."

Here the client detail service is configured. OAuth framework automatically associates the client:secret data source to the oauth_client_details table. We only need to feed the data source to the ClientDetailServiceConfiguration. That is what happens here: ClientDetailService bean is created (I.3), then the bean is injected into ClientDetailServiceConfiguration (I.4).

Notice that AuthorizationServerEndpointConfiguration @PostConstruct method is called so early. It is because there is a circular dependency here: AuthorizationServerEndpointConfiguration depends on its configurers (AuthorizationConfig). On the other hand, the configurer depends on  the AuthorizationServerEndpointConfiguration to create a TokenGranter.

Step 2. Filter Chains Initiated

Next, the initialized beans are configured. WebSecurityConfiguration class, which comes with @EnableWebSecurity annotation, governs the process. The configurations are assembled into a single (per server) WebSecurity object.

How WebSecurityConfiguration Works

Fig 3. How WebSecurityConfiguration works. "M" stands for "dependency injection and custom init() method calls."

WebSecurityConfiguration collects all descendants of WebSecurityConfigurer class and feeds them to setFilterChainProxySecurityConfigurer to set the configurers to the WebSecurity object. Then, WebSecurityConfiguration calls springSecurityFilterChain() to build the WebSecurity object with the configurers already set. To build itself, WebSecurity calls various methods from its parents (Fig 3), most importantly, the init(...), configure(...), build(...) of WebSecurityConfigurerAdapter (a child of WebSecurityConfigurer). 

WebSecurity.build() eventually creates a securityFilterChain for every descendant of WebSecurityConfigurerAdapter. There are 3 such adapters in our system: AuthorizationConfig, SecurityConfig, H2Config. For the second one, we don't use its corresponding filter chain in our system, we just need the adapter's infrastructure to build the client:secret authentication manager. 

Step 3. Primary Filter Chain is Configured

In this step, the system builds a chain for AuthorizationConfig. To do this, AuthorizationServerSecurityConfiguration (a descendant of WebSecurityConfigurer and WebSecurityConfigurerAdapter) calls its init(...), configure(...), and build(...) methods:

How AuthorizationServerSecurityConfiguration is Configured

Fig 4. How AuthorizationServerSecurityConfiguration is configured. The dependencies on the previous steps are also shown.

Here, this WebSecurityConfigurerAdapter.init(WebSecurity web) method creates its HttpSecurity object; there is 1 such object per adapter. On step M.7 AuthorizationConfig.configure(AuthServerSecurityConfigurer security) is called. On step M.8 a securityFilterChain gets built for this descendant of WebSecurityConfigurerAdapter.

Notice that WebSecurity and HttpSecurity behave similarly. It is because they extend the same abstract class. These relations are summarized on Fig 5 (A). The way they work is shown on Fig 5 (B):

Parent-Child Relationships of Some Classes in The System

Fig 5. Parent-child relationships of some classes in the system.

Let's take WebSecurity as an example. The pattern is: 

  1. WebSecurity collects configurers and transfers them to the AbstractConfiguredSecurityBuilder.
  2. The builder calls doBuild() that in turn calls init().
  3. The init iterates over all the configurers and calls each configurer's init method with the WebSecurity web as the argument.
  4. The same for the configure() and performBuild() methods.

Step 4. Username and Password Authorization Manager is Built

Next, the system uses SecurityConfig and its parent adapter to build a username:password authentication manager:

How Authorization Manager Is Built

Fig 6. How Authorization manager is built. Fragments of the previous steps are included.

Here, everything interesting happens in the adapter's getHttp() method.  The authenticationManager() method gets the userDetailsService from SecurityConfig. Then the userDetailsService is fed to a DaoAuthenticationProvider. Finally, we get a fully configured workable username:password authentication manager.

Step 5. Primary Filter Chain is Built

Finally, we need to finish building our primary filter chain where the /oauth/token endpoint is located.

How the client:secret BasicAuthenticationFilter is Configured

Fig 7. How the client:secret BasicAuthenticationFilter is configured.

Here the M.4-M.5 steps are shown in detail. Then, AuthorizationServerSecurityConfigurer sets ClientDetailsService as the userDetailService and puts this service to the AuthenticationManagerBuilder. Next, the init(...) calls HttpBasicConfigurer that sets the manager to BasicAuthenticationFilter . So, our main security filter chain is built. Let's see how all these pieces work together in the token endpoint.

Token Endpoint

The code of this endpoint is stored here. Its key method is:

Java
 
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(
			Principal principal, @RequestParam Map<String, String> parameters)
			throws HttpRequestMethodNotSupportedException {

		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}

		String clientId = getClientId(principal);
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

		// Only validate client details if a client is authenticated during this request.
		// Double check to make sure that the client ID is the same in the token request and authenticated client.
		if (StringUtils.hasText(clientId) && !clientId.equals(tokenRequest.getClientId())) {
			throw new InvalidClientException("Given client ID does not match authenticated client");
		}

		if (authenticatedClient != null) {
			oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
		}

		if (!StringUtils.hasText(tokenRequest.getGrantType())) {
			throw new InvalidRequestException("Missing grant type");
		}

		if (tokenRequest.getGrantType().equals("implicit")) {
			throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
		}

		if (isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
			// The scope was requested or determined during the authorization step
			logger.debug("Clearing scope of incoming token request");
			tokenRequest.setScope(Collections.<String>emptySet());
		} else if (isRefreshTokenRequest(parameters)) {
			if (StringUtils.isEmpty(parameters.get("refresh_token"))) {
				throw new InvalidRequestException("refresh_token parameter not provided");
			}
			// A refresh token has its own default scopes, so we should ignore any added by the factory here.
			tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
		}

		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type");
		}

		return getResponse(token);
	}


Despite looking long, it is an ordinary spring security-protected endpoint. There are a principal and request parameters as input arguments. Notice the principal has the information about the client:secret pair of a user, but not the user's username:password - this is because the BasicAuthenticationFilter is set to check only the client:secret pair. The method validates the client:secret pair, creates TokenRequest, and then calls TokenGranter.grant(tokenRequest.getGrantType(), tokenRequest)  . In our case, this is a composite token granter described in Part 1. Finally, the response is created from the granted OAuth2AccessToken.

Conclusions

In this post, I demonstrated how the Spring OAuth server builds the primary filter chain in 5 steps. These steps are: beans initialized, filter chain initiated, the primary filter chain is configured, username & password authorization manager is built, the primary filter chain is built. Also, I showed how the token endpoint works.

Acknowledgments

I would like to thank my mentor Sergey Suchok for his help on this paper.

Spring Framework authentication Spring Security Spring Boot Filter (software) Build (game engine)

Opinions expressed by DZone contributors are their own.

Related

  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Spring Security Oauth2: Google Login
  • Implementing Your Own Spring Boot Oauth2 Authorization Server

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!