DEV Community

Julien Dubois for Microsoft Azure

Posted on • Updated on

Using Spring Security with Azure Active Directory

Why use Active Directory?

Let's be honnest, Active Directory isn't "cool" today. People see it has very complex, which is true - but security is a complex matter! And it doesn't have the hype of new products like Red Hat's Keycloak, even if both are often used for the same goal, at least with Spring Boot: securing a business application using OpenID Connect.

However, there's one really nice feature of Active Directory: your company has probably it already installed, and probably already pays for it (hint: for a security product, you'd better pay if you want support and the latest patches - that's why I always recommend to pay for Keycloak!). Officially, 85% of Fortune 500 companies are using Active Directory, which is in fact (unofficially!) confirmed by this clever hacker.

So securing a Spring Boot application with Active Directory makes a lot of sense :

  • It's probably already installed, validated and secured in your company
  • You'll have access to the official employee list of your company: no need to create new users, handle lost passwords, invalidate old employees...
  • It's highly secured: you'll have 2 factor authentication, etc, everything is included

But there's a last reason: it's free! Well, not totally free, but if you look at the pricing model, there's a very generous free tier. So you can use it for development or for your start-up idea, without paying anything.

The Azure Spring Boot Starter for Active Directory

Thankfully, the Azure engineering team is providing a Spring Boot Starter for Azure Active Directory, which is available at https://github.com/microsoft/azure-spring-boot/tree/master/azure-spring-boot-starters/azure-active-directory-spring-boot-starter. There are two official tutorials available:

  • The official tutorial is available here.
  • There's also a tutorial in the project GitHub repository, which is updated along with the code: this one works with the latest version of the code, and is less detailed than the official one.

Both of them are a bit outdated at the time of this writing, and as it's my work to update them, I'm first doing this blog post to gather feedback:

  • We'll be using the latest and greatest version of both Spring Boot (2.1.8, released a few hours ago at the time of this writing) and the Azure Spring Boot starters (2.1.7)
  • We'll be using the new Active Directory user interface: this is a huge case of problem here, as all the screenshots currently available are from the older user interface, so you can't find anything easily

Please, don't hesitate to add comments to this blog post, so I can clean up everything, and create an awesome official tutorial!

The sample project

The sample project we do here is available at https://github.com/jdubois/spring-active-directory, so if you want to see the real code and test it, it's all there.

This is a Spring Boot project generated with Spring Initializr as I wanted to have something extremely simple. It uses the following important components:

  • Spring Web
  • OAuth2 Client, which transitively includes Spring Security
  • Azure Support
  • Spring Data JPA and MySQL, so we can build a "real" application in the future

Spring components

Configuring Active Directory

Now is the tricky part of this post! Configuring Active Directory is complicated, so we'll go step-by-step and provide screenshots.

Create your own tenant

Active Directory provides tenants, which are basically instances that you can use. There are two types of instances: work and school (the one I will use here), and social accounts (called "Azure Active Directory B2C").

As discussed earlier, there's a generous free tier, so you can create your own tenant without paying anything:

  • Go to the the Azure portal
  • Select "All resources", and look for "Azure Active Directory" and click "create"
  • Fill in your organization's name, domain and country, and you're done!

Create resource

Accessing your Active Directory tenant

You can now switch to your Active Directory tenant by clicking on the "Directory + Subscription" icon on the top menu:

Switch directory

Configuring your tenant

Once you have switched to your tenant, select "Active Directory", and you can now configure it with full admin rights. Here's what's important to do.

Click on "App registrations" and create a new registration:

create tenant

Please note that:

  • The account type must be "multitenant". The current Spring Boot starter does not work with single tenants, which is an issue being currently addressed.
  • The redirect URI must be "http://localhost:8080/login/oauth2/code/azure". Of course you can replace "localhost:8080" by your own domain, but the suffix is the default one that will be configured by the Spring Boot starter, and if those URI do not match, you will not be able to log in.

Select the registered application

  • In the "overview", note the "Application (client) ID", this is what will be used in Spring Security as "client-id", as well as the "Directory (tenant) ID", which will be Spring Security's "tenant-id".
  • Select "Authentication", and in the Web "Platform configuration", check both options under "Implicit grant" ("Access tokens" and "ID tokens")
    Implicit grant

  • Click on "Certificates & secrets", and create a new client secret, which will be Spring Security's "client-secret"

client secret

  • Click on "API permissions", and under "Microsoft Graph", give your application the "Directory.AccessAsUser.All" and "User.Read" permissions
  • Click on the "Grant admin consent" button at the bottom of the page

API permissions

Go back to your Active Directory tenant and click on "User settings"

Under "Manage how end users launch and view their applications", validate the "Users can consent to apps accessing company data on their behalf" is set to "Yes" (this should be good by default).

consent

Create users and groups

Still in your Active Directory tenant, select "Groups" and create a new group, for example "group1".

Create group

Now select "Users", create a new user, and give that user the "group1" group that we just created.

Create user

Configure the Spring Boot application

Now let's use that configured tenant, with this new user, in our Spring Boot application.

In the pom.xml file, add the azure-active-directory-spring-boot-starter:

        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-active-directory-spring-boot-starter</artifactId>
        </dependency>

In your application.yml file (or application.properties file if you don't like YAML), configure the following properties. Please note that we got the 3 required values when we registered our application in our Active Directory tenant.

azure:
  activedirectory:
    tenant-id: <tenant-id>
    active-directory-groups: group1, group2

spring:
  security:
    oauth2:
      client:
        registration:
          azure:
            client-id: <client-id>
            client-secret: <client-secret>

We now need to configure our Spring Boot application to use Active Directory. Create a new SecurityConfiguration class:

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;

    public SecurityConfiguration(OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService) {
        this.oidcUserService = oidcUserService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .oauth2Login()
                .userInfoEndpoint()
                .oidcUserService(oidcUserService);
    }
}

This configuration will require that each request is secured, and will therefore redirect any user to Active Directory when he tries to connect.

Running everything

When running the Spring Boot application, we recommend you add a src/main/resources/logback-spring.xmllogback-spring.xml file with the following configuration, in order to better understand the issues you might encounter:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>

<configuration scan="true">
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <logger name="org.springframework.security" level="DEBUG"/>
    <logger name="com.microsoft" level="DEBUG"/>
    <logger name="com.example" level="DEBUG"/>

</configuration>

Now if you run the application, accessing it on http://localhost:8080 should point you to Active Directory, where you can sign it using the user we just created earlier:

Log in

Testing the security

In order to know if everything is correct, including if our user really receive the "group1" role that we configured earlier, let's add a specific Spring MVC controller called AccountRessource:

package com.example.demo;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AccountResource {

    @GetMapping("/account")
    public Authentication getAccount() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

Accessing http://localhost:8080/account should now give you all the user's security information, including his roles! Congratulations, you have secured your Spring Boot application using Active Directory!

Top comments (27)

Collapse
 
ikwattro profile image
Christophe Willemsen • Edited

Thanks for this wonderful blog post Julien. We had to check the ID Tokens here

img

in order to have the full integration working. Without that it was throwing the following exception :

com.microsoft.aad.adal4j.AuthenticationException: {"error_description":"AADSTS240002: Input id_token cannot be used as 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant.\r\nTrace ID: 5c2df3b6-23d6-44b8-8c3d-37c6777d6000\r\nCorrelation ID: a5c7ee8d-90da-457d-8e3c-739faadfeed5\r\nTimestamp: 2019-09-10 08:35:44Z","error":"invalid_request"}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jdubois profile image
Julien Dubois

Thanks Christophe!! Yes you are totally correct! I don't understand, I remember perfectly well to have put this in my article, but it doesn't show up. Let me correct this ASAP!!!

Collapse
 
jdubois profile image
Julien Dubois

It's fixed, thanks again Christophe!

Collapse
 
wimdeblauwe profile image
Wim Deblauwe

Is it correct to state that this only works because the default role in Azure AD is 'User' and in Spring it is 'ROLE_USER' ? What if I create a 'ROLE_ADMIN' in my Spring Boot app? Do I need to use Azure AD Premium P1 or P2 to be able to do that? Or is it possible in the free version as well?

Collapse
 
jdubois profile image
Julien Dubois

Oh yes, this works because we have the same roles, and I also think that those can only be modified in the premium tier, at least for now. I'm not an expert in those tiers, so I can't tell you if there's a trick to do it for free.

Collapse
 
joshuaoh profile image
YOH • Edited

Declaring app roles using Azure portal for free

docs.microsoft.com/en-us/azure/act...

stackoverflow.com/questions/556090...

Collapse
 
cowinr profile image
Richard Cowin

I'd love to see how you get on with configuring access to your app when it's deployed to Azure App Service and using a non-localhost domain over HTTPS. That's where I encountered issues trying to configure the reply URL as per dev.to/cowinr/setting-up-spring-se...

Interesting to see your note "The current Spring Boot starter does not work with single tenants, which is an issue being currently addressed." I set mine up as a single tenant registration and it worked after a fashion. Perhaps I'll have better luck configuring as a multi-tenant registration.

Collapse
 
fokkog profile image
Fokko Groenenboom

I'm late to this party, but I was following the above walkthrough last weekend including a deploy to Azure App Service. Locally it worked from the get-go whereas in the cloud I got the exact same AADSTS50011 error, due to a mismatch between http (suggested) vs https (registered) reply URL's.
Apparently this is a well-known issue due to the fact that in the cloud, the Spring Boot application (running on http) is proxied by IIS (running on https). See also Running Behind a Front-end Proxy Server and Deploy Your Spring Boot Application to Azure. The suggested addition of this snippet to application.yaml solved it for me:

server:
  forward-headers-strategy: FRAMEWORK
Collapse
 
jdubois profile image
Julien Dubois

Thanks! For single tenant there seems to be a separate documentation, I need to work on it, I don't understand why it should be a different configuration from Spring Boot

Collapse
 
motolola profile image
Motolola

After following this simple configuration for my Spring boot system, I just keep getting this error,
Still trying to figure out why.

java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2WebSecurityConfiguration.authorizedClientRepository
at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:60) ~[spring-boot-autoconfigure-2.2.2.RELEASE.jar:2.2.2.RELEASE]

Collapse
 
livefreesuman profile image
Suman Mitra

In a client-directed-flow I want to write a custom identity provider where I would like to use my service account. Iam writing a spring service and from there I would like to signs a service account to be validated against AAD to get the token. I want to avoid the popup. Is it possible?

Collapse
 
jdubois profile image
Julien Dubois

In that case, you are not authenticating using the client's credentials, so showing the popup doesn't make any sense, am I correct? Also, having everybody use a service account looks like a big security issue - for instance, you won't be able to audit what people did, and also everybody will have the same permissions. And service accounts usually have higher privileges than normal user accounts. Are you sure this is a good idea?
Anyway, if this is correct, this would work like a usual OAuth2 flow between two applications: you need to store the secret token somewhere secured (Azure Key Vault?), and then you can use it to access whatever service you need. There's no need to have a login popup for this.

Collapse
 
ivan86to profile image
Ivan

Thanks for the tutorial. Is it possible to use this tutorial on JHipster 6.5.1?

Collapse
 
jdubois profile image
Julien Dubois

Thanks! I haven't tried it, but there shouldn't be any difference from using start.spring.io, so yes it should work the same. You'll need to tweak the SecurityConfiguration class probably - have a look at the OICD option, it should be pretty close to what you need. Oh, and please send an update if you succeed! Maybe a tip on jhipster.tech/tips/ ?

Collapse
 
ivan86to profile image
Ivan • Edited

Hello Julien,
in my git repository i have published a JHipster 6.5.1 Project with the
implementation of Login by Azure Active Directory.

Function: Login / Logout

When and if you have time you can review the code!
Thanks a lot for this tutorial!

This is the link github.com/ivan86to/jhipster-ad-azure

Thread Thread
 
jdubois profile image
Julien Dubois

Thanks a lot!! This should be a new security option in JHipster, we need to automate this. Would you be interested in contributing this? It's mostly a matter of transforming your existing code into templates. Or at the minimum this should be in our tips section.

Collapse
 
ivan86to profile image
Ivan

Ok, login work success with less modifications :)
But the Logout resource not work because
this.registration.getProviderDetails().getConfigurationMetadata()
.get("end_session_endpoint") return null.
I try to fix this problem
Tnk!

Collapse
 
shrabangit profile image
shraban-git • Edited

Hi. I tried this . It's working fine, if I enter localhost:8080 in browser. But if I try with ui(angular get request to 8080). It error with cors issue. Already tried with spring security cors. Also in angular with header cors. Nothing worked. Redirection is not working.from ui login azure not opening error cors. Href is working. But again we need to redirect to angular. Is there any solution.

Collapse
 
kowshikns profile image
kowshikns • Edited

Exactly what I wanted. Thanks a mill for this article.
Could help me creating one simple postman request ( example 1. Create Login/ 2. an API with Authorization Token) please.I am stuck at this point.
Thanks

Collapse
 
jdubois profile image
Julien Dubois

Thanks @kowshikns !
I have never used Postman with AAD, but I have found this documentation that looks correct: github.com/MicrosoftDocs/azure-doc...
Oh, and have you tried Postwoman instead? github.com/liyasthomas/postwoman

Collapse
 
wimdeblauwe profile image
Wim Deblauwe

Thanks for this. Is there or could you write a similar post, but for a mobile application for example? So that login can happen without going to a web page?

Collapse
 
jdubois profile image
Julien Dubois

There are many types of mobile applications, like iOS or Android, I can't do every of them, sorry about that. But it shouldn't change much, it's the same flow for all technologies.

Collapse
 
louisthomas profile image
ltlamontagne

Hi,

Is it possible to retrieve userinfo (connect2id.com/products/server/doc...) with custom parameters inside SecurityContextHolder?

Thanks

Collapse
 
jdubois profile image
Julien Dubois

I don't think there's a limitation here, I do see all the user information, roles, etc. So that should work.

Collapse
 
2488275582 profile image
2488275582 • Edited

Hello,I have upgraded to spring-azure this framework, but my application uses multi azure account, so is multi-tenantcode dynamic switchover supported?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.