iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🍃

Using Azure Active Directory OAuth2 Authentication in Spring Boot

に公開

Let's try using Azure Active Directory's OAuth2 authentication in Spring Boot.
Since a Spring Boot Starter for Azure Active Directory is available, it can be implemented easily.

The entire project used for this confirmation is located here:

Preparation on the Azure Active Directory Side

You need to prepare the following on the Azure Active Directory side:

  • (If a tenant is not yet created) Create a tenant
  • From "App registrations":
    • Register the target application using "New registration"
      • Set the redirect URI to http://localhost:8080/login/oauth2/code/
    • Create a new client secret in "Certificates & secrets"
    • Create app roles from "App roles"
  • (If a user is not yet created) Create a user
  • Select the registered application in "Enterprise applications," choose "Add user/group" from "Users and groups," and assign the user + role combination.

For detailed operations, the following is helpful:

Project

I will confirm this with Spring Boot 2.7.5, which is the latest version (as of October 23, 2022).

  • Java 17
  • Spring Boot 2.7.5
  • Gradle 7.5

build.gradle is as follows:

build.gradle
plugins {
    id 'org.springframework.boot' version '2.7.5'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
}

group = 'com.github.onozaty'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

ext {
    set('springCloudAzureVersion', "4.3.0") // Because there is a problem in 4.4.0 and it doesn't work
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.azure.spring:spring-cloud-azure-starter-active-directory'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "com.azure.spring:spring-cloud-azure-dependencies:${springCloudAzureVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

The latest version of spring-cloud-azure-starter is 4.4.0, but there is a problem in 4.4.0 that causes an error in OAuth2. Since there is no problem in the previous version 4.3.0, I am using 4.3.0 to avoid it.

Configuring Azure Active Directory Information on the Application Side

Configure the Active Directory information. Below is an example.

src/main/resources/application.properties
# Enable related features.
spring.cloud.azure.active-directory.enabled=true
# Specifies your Active Directory ID:
spring.cloud.azure.active-directory.profile.tenant-id=22222222-2222-2222-2222-222222222222
# Specifies your App Registration's Application ID:
spring.cloud.azure.active-directory.credential.client-id=11111111-1111-1111-1111-1111111111111111
# Specifies your App Registration's secret key:
spring.cloud.azure.active-directory.credential.client-secret=AbCdEfGhIjKlMnOpQrStUvWxYz==

Creating Simple Web Pages

Create the following three HTML files to verify access control.

  • src/main/resources/static/index.html Top page
  • src/main/resources/static/admin/admin.html Page for admins
  • src/main/resources/static/user/user.html Page for users
src/main/resources/static/index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Top</title>
  </head>
  <body>
    <ul>
      <li><a href="./admin/admin.html">Admin</a></li>
      <li><a href="./user/user.html">User</a></li>
      <li><a href="./logout">Logout</a></li>
    </ul>
  </body>
</html>
src/main/resources/static/admin/admin.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Admin</title>
  </head>
  <body>
    <ul>
      <li><a href="../">Top</a></li>
      <li><a href="../user/user.html">User</a></li>
      <li><a href="../logout">Logout</a></li>
    </ul>
  </body>
</html>
src/main/resources/static/user/user.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>User</title>
  </head>
  <body>
    <ul>
      <li><a href="../">Top</a></li>
      <li><a href="../admin/admin.html">Admin</a></li>
      <li><a href="../logout">Logout</a></li>
    </ul>
  </body>
</html>

Spring Security Configuration

To restrict access based on roles, we will set up a custom security configuration.

src/main/java/com/example/azure/AzureAdOAuthLoginSecurityConfig.java
package com.example.azure;

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.util.matcher.AntPathRequestMatcher;

import com.azure.spring.cloud.autoconfigure.aad.AadWebSecurityConfigurerAdapter;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AzureAdOAuthLoginSecurityConfig extends AadWebSecurityConfigurerAdapter {

    // https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/spring-boot-starter-for-azure-active-directory-developer-guide#protect-a-resource-serverapi
    // If you want a custom security configuration, extend and specify AadWebSecurityConfigurerAdapter
    // (The default is configured with DefaultAadResourceServerWebSecurityConfigurerAdapter)

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        // https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
        http.authorizeHttpRequests(
                authz -> authz
                        // Under /admin/** requires the admin role (the name specified in the app roles is prefixed with APPROLE_)
                        .mvcMatchers("/admin/**").hasAnyAuthority("APPROLE_admin")
                        // Under /user/** requires the user role
                        .mvcMatchers("/user/**").hasAnyAuthority("APPROLE_user")
                        // All other requests also require authentication (any role is fine (or no role is fine))
                        .anyRequest().authenticated());

        http.logout(
                // Allow logout to be accepted via GET
                logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")));
    }
}

Operation Check

Log in with a user who has both the admin and user roles assigned in the app roles. Since they have the authority, the Admin page is displayed without any issues.

Log in with a user who has only the user role assigned. Navigating to the Admin page results in a 403 Forbidden because they lack the necessary authority.

Reference

Discussion