With OAuth2 being the current de-facto authorization framework, a lot of vendors use it to secure their APIs. Furthermore, you can use OAuth2 to enable social logins (e.g. Google or Facebook) and donโt need your own user management. As the WebClient
from Spring WebFlux is the preferred client for Spring applications, I want to provide an example for the Spring WebClient OAuth2 setup. As an example, Iโll use for an OAuth2 login and will access an OAuth2 protected API using the WebClient
.
OAuth2 Spring WebFlux project setup
The Maven project for this example contains the required Spring Boot dependencies for Thymeleaf, WebFlux, Security and the OAuth2 client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.rieckpil.blog</groupId> <artifactId>spring-web-client-oauth2</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-web-client-oauth2</name> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
To use for OAuth2 login and for accessing their protected APIs, we have to configure the application as an OAuth2 client. Within the application.yml
file, add the client-id
and client-secret
that you receive while creating an OAuth2 app on (follow this tutorial).
1 2 3 4 5 6 7 8 9 | spring: security: oauth2: client: registration: : client-id: replace-me client-secret: replace-me scope: read:user,public_repo |
Fortunately, Spring Security ships with information about the OAuth2 related endpoints for Google, Facebook and . Given this fact, we donโt have to configure any additional URLs. The client registration key in the
application.yml
file above tells Spring to use .
For those of you who are interested in which class Spring configures these endpoints, have a look at the CommonOAuth2Provider
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { @Override public Builder getBuilder(String registrationId) { ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL); builder.scope("read:user"); builder.authorizationUri("https://.com/login/oauth/authorize"); builder.tokenUri("https://.com/login/oauth/access_token"); builder.userInfoUri("https://api..com/user"); builder.userNameAttributeName("id"); builder.clientName(""); return builder; } } |
Securing the WebFlux application with OAuth2 login
Next, we have to secure our application. As we donโt want any unauthenticated users to access the application,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Configuration public class SecurityConfiguration { @Bean SecurityWebFilterChain configure(ServerHttpSecurity http) { return http .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated() ) .oauth2Login(withDefaults()) .oauth2Client(withDefaults()) .build(); } } |
Please note that this configuration is for using Spring WebFlux. If you are using the Servlet environment with Spring Web and Tomcat, the configuration is different as you have to extend the WebSecurityConfigurerAdapter
class.
WebClient OAuth2 configuration
With the application security setup in place, we can continue with the configuration for the WebClient
. The Spring Security OAuth2 client dependency provides a ServerOAuth2AuthorizedClientExchangeFilterFunction
, which we can use to configure our WebClient
instance. This filter is for working with the reactive web stack, for the Servlet stack, have a look at the ServletOAuth2AuthorizedClientExchangeFilterFunction
.
Moreover, as the filter function takes a ReactiveOAuth2AuthorizedClientManager
as an input argument, we have to provide this bean. To create such a bean we can inject the ReactiveClientRegistrationRepository
which gets autoconfigured (as we use Spring Boot Security) based on our application.yml
.
The API uses the authorization_code
OAuth2 flow, so we can configure the ReactiveOAuth2AuthorizedClientProvider
for only this flow. Other flows like client_credentials
, refresh_token
are also available.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | @Configuration public class WebClientConfig { @Bean public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) { ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); return WebClient.builder() .filter(oauth) .build(); } @Bean public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .build(); DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; } } |
Accessing OAuth2 protected resources on
Last but not least, weโll add a Thymeleaf page to demonstrate the OAuth2 login and the result of the API access with Springโs WebClient
. The page display the name of the logged-in user and all of the userโs repositories:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>OAuth2 WebClient</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h3 th:text="' repositories of ' + ${username}"></h3> <p>Only the first 100 repositories are shown</p> <ol> <li th:each="repository : ${repositories}" th:text="${repository}"></li> </ol> <code th:text="${result}"> </code> </div> </div> </div> </body> </html> |
The backend controller takes an OAuth2AuthorizedClient
as a method argument alongside the authenticated principal and the Spring MVC Model
class. Once a user accesses our page, a redirect to takes place and the user has to provide his credentials:
After successfully verifies the credentials, the user gets redirected to our application and Spring Security will exchange the authorization code for an access token in the background.
Given this token, we can access the API with the WebClient
and query for the userโs repositories:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @Controller @RequestMapping("/") public class Controller { private static final String _API_URL = "https://api..com"; private WebClient webClient; public Controller(WebClient webClient) { this.webClient = webClient; } @GetMapping public String index(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient, @AuthenticationPrincipal OAuth2User oauth2User, Model model) { model.addAttribute("repositories", fetchAllRepositories(authorizedClient)); model.addAttribute("username", oauth2User.getAttributes().get("login")); return "index"; } private Flux<String> fetchAllRepositories(OAuth2AuthorizedClient authorizedClient) { return this.webClient .get() .uri(_API_URL, uriBuilder -> uriBuilder .path("/user/repos") .queryParam("per_page", 100) .build() ) .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(new ParameterizedTypeReference<List<JsonNode>>() {}) .flatMapMany(Flux::fromIterable) .map(jsonNode -> jsonNode.get("full_name").asText()); } } |
The final result looks like the following:
In conclusion, with the correct setup, there is almost nothing to do for an OAuth2 integration. Spring Security in combination with Spring Boot takes care of all the configuration. Again, please note that this example is for using a reactive Web stack and not the Servlet stack. For a Spring Web setup with Tomcat, have a look at this post to configure the WebClient for OAuth2.
You can find the source code with further instructions on how to run this application on .
Have fun using the OAuth2 with the Spring WebClient for a WebFlux application,
Phil