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
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Spring Security Oauth2: Google Login
  • Basic Authentication Using Spring Boot Security: A Step-By-Step Guide

Trending

  • AI Paradigm Shift: Analytics Without SQL
  • What Is Plagiarism? How to Avoid It and Cite Sources
  • Feature Flag Debt: Performance Impact in Enterprise Applications
  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  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
7.1K 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
  • Basic Authentication Using Spring Boot Security: A Step-By-Step Guide

Partner Resources

×

Comments

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

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

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 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook