Spring Security, Powered by MSAL Part 3 (Client Auth and Resource Server)

Adeogo Oladipo
2 min readMay 8, 2022

--

In the Part 1 and Part 2 of this series, we discussed two common Auth use cases; Client Auth Mode and the Resource Server Mode of integrating MSAL with Spring Boot.
In this article, we will look at another common use case; the Client Auth and Resource Server mode.

Use Case

Let’s say you have an application with and API endpoint you would like to expose to another application in your organization. And also in the same application, you want to allow users from your Azure Active Directory to have access to some other routes in the application.

App Registrations Setup

We would follow the App Registrations Setup described in the Second article in this Series.

Code Implementation

Dependencies

<properties>
<java.version>11</java.version>
<azure.version>3.11.0</azure.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-active-directory</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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

application.yml

azure:
activedirectory:
application-type: web_application_and_resource_server
app-id-uri: ${APP_ID_URI}
tenant-id: ${AZURE_TENANT_ID}
client-id: ${AZURE_CLIENT_ID}
client-secret: ${AZURE_CLIENT_SECRET}
authorization-clients:
azure:
authorization-grant-type: authorization_code
scopes: https://graph.microsoft.com/User.Read

SecurityAdapter

package com.example.demosecurity;

import com.azure.spring.aad.webapi.AADResourceServerWebSecurityConfigurerAdapter;
import com.azure.spring.aad.webapp.AADWebSecurityConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;


@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2Config {

private static final String APPROLE_ROLE_1 = "APPROLE_ROLE_1";
private static final String APPROLE_ROLE_2 = "APPROLE_ROLE_2";

@Order(1)
@Configuration
public static class ApiWebSecurityConfigurationAdapter extends AADResourceServerWebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.antMatcher("/api/**").authorizeRequests().anyRequest().hasAnyAuthority(APPROLE_ROLE_1, APPROLE_ROLE_2).and().oauth2Login();
}
}

@Configuration
public static class HtmlWebSecurityConfigurerAdapter extends AADWebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.requireCsrfProtectionMatcher(httpServletRequest -> false)
.and()
.authorizeRequests()
.antMatchers("/actuator/health", "/public/*").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
}

}

Controller

@RestController
public class DemoController {

@GetMapping(value = "/demo")
public String demo() {
return "demo";
}

@GetMapping(value = "/api/demo")
public String apiDemo() {
return "demo";
}
}

Conclusion

With this setup, the regular user can view all the routes except the routes starting with /api, but the Api consumer can only access routes starting with /api.

The source code for this article can be found on Github.

--

--

Adeogo Oladipo
Adeogo Oladipo

Written by Adeogo Oladipo

Co-Founder and CTO @DokitariUG. A Strong believer in the Potential in Each Human.

No responses yet