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
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!
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:
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:
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")
Click on "Certificates & secrets", and create a new client secret, which will be Spring Security's "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
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).
Create users and groups
Still in your Active Directory tenant, select "Groups" and create a new group, for example "group1".
Now select "Users", create a new user, and give that user the "group1" group that we just created.
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:
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)
Thanks for this wonderful blog post Julien. We had to check the ID Tokens here
in order to have the full integration working. Without that it was throwing the following exception :
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!!!
It's fixed, thanks again Christophe!
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?
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.
Declaring app roles using Azure portal for free
docs.microsoft.com/en-us/azure/act...
stackoverflow.com/questions/556090...
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.
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:
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
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]
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?
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.
Thanks for the tutorial. Is it possible to use this tutorial on JHipster 6.5.1?
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/ ?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
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.
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!
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.
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
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
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?
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.
Hi,
Is it possible to retrieve userinfo (connect2id.com/products/server/doc...) with custom parameters inside SecurityContextHolder?
Thanks
I don't think there's a limitation here, I do see all the user information, roles, etc. So that should work.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.