Spring Security, Powered by MSAL Part 3 (Client Auth and Resource Server)
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.