Click here to Skip to main content
15,123,619 members
Articles / Hosted Services / Azure
Article
Posted 18 Oct 2021

Stats

1.5K views
4 bookmarked

Cross-Solution Microsoft Identity for Java Developers Part 1: Adding Authentication to a Spring Cloud Java Application Using Microsoft Authentication Library

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
18 Oct 2021CPOL7 min read
In this article we’ll explore integrating Azure AD and MSAL with a Spring Boot web application.
Here we register an application with Azure AD and wrote a simple web application to authenticate users via their Azure credentials.

Every developer has to address authentication and authorization at some point in enterprise application development. These security concerns are complex problems to solve. Yet, generally speaking, most applications have similar requirements. These include:

  • Logging users into and out of an application
  • Resetting passwords
  • Managing user permissions
  • Supporting logins from existing accounts, including Google, Facebook, Microsoft, and others
  • Supporting additional security layers, such as two-factor authentication

Azure Active Directory (Azure AD) provides scalable and secure identity platforms and has rich integrations with third-party APIs and identity providers. The Microsoft Authentication Library (MSAL) offers native integration with Azure AD for various languages and frameworks. The combination of Azure AD and MSAL offers developers a turn-key solution for implementing authentication and authorization in our applications.

In this first tutorial of a three-part series, we’ll explore integrating Azure AD and MSAL with a Spring Boot web application. This process involves:

  • Registering a new Azure AD application
  • Creating a new application secret
  • Bootstrapping a Spring application
  • Configuring the Spring application with the Azure AD tenant
  • Logging in with an external account and inspecting the logged-in user’s details

Find this tutorial’s frontend application source code in the mcasperson/SpringMSALDemo GitHub repo under the initial-integration branch. To follow along, you should know Java.

Registering an Azure AD Application

Our application needs to be registered with Azure AD to manage user logins and perform other user management tasks. If you don’t already have an Azure account, sign up to access services like Azure AD. You get 50,000 stored objects monthly with a single sign-on (SSO) to all cloud apps for free.

Start by signing into the Azure Portal. Then, use the search bar at the top of the screen to search for Azure Active Directory:

Image 1

To register our application with Azure AD, we click the App Registration link in our Azure Active Directory left-hand menu, then click the New registration button:

Image 2

Then, we name the application:

Image 3

Next, we enter http://localhost:8080/login/oauth2/code/ for the Redirect URI. A Spring Boot application integrated with MSAL exposes this default URI.

Then, we click the Register button:

Image 4

Make a note of your Directory (tenant) ID and Application (client) ID, as you’ll need these values later on.

To connect our code to the application, we need to generate a secret. To do this, we click the Client credentials link:

Image 5

Then, we name the secret and click the Add button:

Image 6

Make a note of your secret Value, as Azure won’t show it again once you’ve left this screen:

Image 7

Building the Spring Boot Application

With Azure AD now configured, we’re ready to start building our Spring application. We use Spring's online tool as a starting point for our code.

Bootstrapping the Spring Application

We start by open Spring Initializr, an online tool for creating starter Spring projects to bootstrap our application. We select options there to build a JAR file with Maven and Java 17, using the latest non-snapshot version of Spring. We also click ADD DEPENDENCIES to add the following three dependencies:

  • Spring Web, a built-in webserver to host our application
  • Thymeleaf, a template language for displaying dynamic web pages
  • Azure Active Directory, enabling us to integrate with Azure AD

Next, we click the GENERATE button to download a ZIP file with the bootstrapped project.

Image 8

Configuring the Azure AD Settings

We need to configure the Azure application in the application.yml file. The contents of this file are as follows:

# src/main/resources/application.yml
 
azure:
  activedirectory:
    tenant-id: ${TENANT_ID}
    client-id: ${CLIENT_ID}
    client-secret: ${CLIENT_SECRET}
 
logging:
  level:
    org:
      springframework:
        security: DEBUG

Let’s break down these values:

  • tenant-id is the Tenant ID we used to register our Azure AD application. We’ll define this value as an environment variable called TENANT_ID.
  • client-id is the Client ID from our Azure AD application. We’ll define this value as an environment variable called CLIENT_ID.
  • client-secret is the secret we created for the Azure AD application. We’ll define this value as an environment variable called CLIENT_SECRET.
  • The Spring Security library is set to print logs at the DEBUG level, which helps diagnose issues with logins and permissions.

Building the Controllers

We need to add controllers to our application to respond to requests from a browser. Spring controllers are regular classes enriched with Spring annotations to integrate them into the Spring Web framework.

The first controller displays the root directory.

Here we have a class called HomeController. It’s annotated with @Controller, with a single method called main annotated with @GetMapping. The parameter passed to the @GetMapping annotation defines the path that this method responds to for HTTP GET requests. Here, we use a single forward slash to indicate that this method responds to requests for the root directory.

The main method returns a string indicating the filename to return to the browser. The filename has no extension, and Spring finds the appropriate file for us. Here’s the controller’s complete code:

Java
// src/main/java/com/matthewcasperson/demo/controllers/HomeController.java
 
package com.matthewcasperson.demo.controllers;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
 
@Controller
public class HomeController {
    @GetMapping("/")
    public String main() {
        return "index";
    }
}

Our application has a second page displaying the details of the currently logged-in user Azure AD. To display this page, we create a second controller called ProfileController. The complete code is:

Java
// src/main/java/com/matthewcasperson/demo/controllers/ProfileController.java
 
package com.matthewcasperson.demo.controllers;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
 
@Controller
public class ProfileController {
 
    @GetMapping("/profile")
    public ModelAndView profile(
            @AuthenticationPrincipal OidcUser principal) {
 
        ModelAndView mav = new ModelAndView("profile");
        mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
        return mav;
    }
 
    private String getObjectAsJSON(Object attributes) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
        } catch (JsonProcessingException e) {
            return "{\"message\":\"" + e + "\"}";
        }
    }
}

Let’s look at this code.

As with the previous controller, ProfileController is a class annotated with @Controller:

Java
@Controller
public class ProfileController {

A method called profile responds to requests to the /profile URL and populates a ModelAndView object.

A ModelAndView contains attributes the view consumes, allowing us to inject dynamic values into a template. Here, we create a ModelAndView object displaying the template called profile and a model attribute called tokenAttributes containing a JSON representation of the attributes assigned to the currently logged-in user:

Java
@GetMapping("/profile")
public ModelAndView profile(
        @AuthenticationPrincipal OidcUser principal) {

    ModelAndView mav = new ModelAndView("profile");
    mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
    return mav;
}

The getObjectAsJSON method converts any object into its JSON representation using the Jackson library:

Java
    private String getObjectAsJSON(Object attributes) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
        } catch (JsonProcessingException e) {
            return "{\"message\":\"" + e + "\"}";
        }
    }
}

Building the Frontend HTML

We won’t dive into the frontend HTML this application uses, as it’s comprised of standard HTML, CSS, and JavaScript. The only exception to this is the Thymeleaf template syntax that displays the model-stored attributes. We’ll dive into this below.

You can find the CSS and JavaScript files in the src/main/resources/static directory and the HTML templates in the src/main/resources/templates directory.

Let’s look at how the tokenAttributes model attribute, defined in the profile method of the ProfileController class, is consumed.

The profile.html page includes the <pre> block shown below. Thymeleaf modifies HTML elements using custom attributes, and here we use the th:text attribute to populate the <pre> element text with the tokenAttributes model attribute’s value:

<pre th:text="${tokenAttributes}"></pre>

Customizing the Security Rules

The default Spring security rules require users to log in to access any page or its supporting CSS and JavaScript files. This setup is a little too restrictive for us, so let’s define custom rules that allow unauthenticated users to view the root directory and require users to log in to view the profile page. We define these rules in the AuthSecurityConfig class:

Java
// src/main/java/com/matthewcasperson/demo/configuration/AuthSecurityConfig.java
 
package com.matthewcasperson.demo.configuration;
 
import com.azure.spring.aad.webapp.AADWebSecurityConfigurerAdapter;
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;
 
@EnableWebSecurity
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        // @formatter:off
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
                .anyRequest().authenticated();
        // @formatter:on
    }
}

The class is annotated with @EnableWebSecurity to enable Spring Security and extends AADWebSecurityConfigurerAdapter, which provides us with the configure method to define the security rules:

Java
@EnableWebSecurity 
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {

We define our application’s security rules in the configure method.

We start by calling the super class. This class allows the AADWebSecurityConfigurerAdapter class to apply the settings the MSAL library requires.

We then call the HttpSecurity object passed into the method, which exposes a fluent interface for configuring application security.

The authorizeRequests method exposes child methods, enabling us to restrict or permit access to paths our application reveals. The application applies the first matching rule defined under authorizeRequests.

Calling antMatchers allows us to list paths that will have rules applied, and permitAll enables unauthenticated users to access the listed paths. We allow all access to the root directory, the /login path, and CSS and JavaScript files.

The anyRequest method matches all requests made to the application. We use this to catch all other requests not granted above. Calling authenticated ensures the user must be logged in to make these requests:

Java
@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    // @formatter:off
    http
        .authorizeRequests()
            .antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
            .anyRequest().authenticated();
    // @formatter:on
}

Running the Spring Application

To build and run the application, we run the following PowerShell:

PowerShell
$env:CLIENT_SECRET="Application client secret"
$env:CLIENT_ID="Application client ID"
$env:TENANT_ID="Azure AD tenant ID"
.\mvnw spring-boot:run

Or Bash:

Bash
export CLIENT_SECRET="Application client secret"
export CLIENT_ID="Application client ID"
export TENANT_ID="Azure AD tenant ID"
./mvnw spring-boot:run

We next open http://localhost:8080/profile. Our browser redirects us to log in using an existing Microsoft account:

Image 9

Once we approve access, our browser redirects to our Spring application.

The profile page then shows the attributes assigned to the currently logged in user:

Image 10

Below is an example attributes list:

{
  "sub" : "EwG-OFUmFOJpv4JUUA0lDtl9QDFKkU9BMkIrIh17aTM",
  "ver" : "2.0",
  "iss" : "https://login.microsoftonline.com/2ed832a8-cd8c-4f7d-89b3-935447e260c8/v2.0",
  "oid" : "4c037c2f-4b2f-46c1-a25f-d214e2396d47",
  "preferred_username" : "matthewcasperson@matthewcasperson.onmicrosoft.com",
  "uti" : "JmhbvuE6ZUGorI55pGkqAA",
  "nonce" : "JQTmD6Yr2SDmxmymWXeSX98pWzQy8UOIwWhQDNwpnrc",
  "tid" : "2ed832a8-cd8c-4f7d-89b3-935447e260c8",
  "aud" : [ "b31490c0-4ac4-4f8c-8f8e-d5addb72271d" ],
  "nbf" : 1632542348000,
  "rh" : "0.AUEAqDLYLozNfU-Js5NUR-JgyMCQFLPESoxPj47VrdtyJx1BACU.",
  "name" : "Matthew Casperson",
  "exp" : 1632546248.000000000,
  "iat" : 1632542348.000000000
}

Next Steps

In this tutorial, we registered an application with an Azure AD tenant, created a simple Spring Boot application that logged in via Azure AD, and displayed information about the currently logged-in user.

In the next article of this three-part series, we’ll use the access token this application receives to make requests to another Spring-based microservice. That microservice acts on the user's behalf to get calendar events from their Office 365 account. Continue this tutorial in the following article, Using MSAL with the Microsoft Graph and Office 365 Data.

Further Reading

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matthew Casperson
Technical Writer
Australia Australia

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA19-Oct-21 15:31
professionalȘtefan-Mihai MOGA19-Oct-21 15:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Cross-Solution Microsoft Identity for Java Developers