I have created a demo spring boot application where i want to use AD authentication and authorization using AD and spring security.Looking at Azure docs i did the following
package com.myapp.contactdb.contactfinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/directory")
public interface Directory {
@Autowired
@PreAuthorize("hasRole('Users')")
@GetMapping("/contact/{mobile}")
public String getContact(@PathVariable("mobile") Long mobile);
@Autowired
@GetMapping("/contact/data")
public String getData();
}
which is the rest API entry point. I created groups and users in it in the respective Azure AD.And used that group as specified in azure docs like this
package com.myapp.contactdb;
import org.springframework.beans.factory.annotation.Autowired;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.microsoft.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
}
}
and app properties as
spring.main.banner-mode=off
# create and drop tables and sequences, loads import.sql
#spring.jpa.hibernate.ddl-auto=create-drop
# MySql settings
spring.datasource.url=jdbc:mysql://localhost:3306/xxxx
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
# HikariCP settings
# spring.datasource.hikari.*
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.maximum-pool-size=5
# azure.activedirectory.tenant-id
azure.activedirectory.tenant-id = xxxx
azure.activedirectory.client-id = xxxx
# spring.security.oauth2.client.registration.azure.client-id
spring.security.oauth2.client.registration.azure.client-id = xxxxxxx
# spring.security.oauth2.client.registration.azure.client-secret
spring.security.oauth2.client.registration.azure.client-secret = xxxxxxxx
azure.activedirectory.active-directory-groups = Users
However i require to authorize using custom roles.I have added an azure premium AD free trial and created a role viz., "Operator". However problem is what property do i use to depict that in the app.props file and how to get the role to get reflected in the @Preauthorize(hasRole('Operator')). Any idea or anything that i may have not seen?
@Jim,
So finally i went with this by modifying the WebSecurityConfig class from above in the question
package com.xxx.contactdb;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import net.minidev.json.JSONArray;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().oauth2Login().userInfoEndpoint()
.oidcUserService(this.oidcUserService());
}
/**
* Replaces the granted authorities value received in token with the roles value
* in token received from the app roles attribute defined in manifest and
* creates a new OIDCUser with updated mappedAuthorities
*
* @return oidcUser
*/
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
oidcUser.getAuthorities().forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
Map<String, Object> userInfo = oidcUserAuthority.getAttributes();
JSONArray roles = null;
if (userInfo.containsKey("roles")) {
try {
roles = (JSONArray) userInfo.get("roles");
roles.forEach(s -> {
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + (String) s));
});
} catch (Exception e) {
// Replace this with logger during implementation
e.printStackTrace();
}
}
}
});
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
return oidcUser;
};
}
}
I did this change for spring boot version 2.3.1 Release which uses Azure 2.3.1 and spring security version 5.3.3. Mentoned this because for Spring boot version 2.1.13 we could use UserAuthoritiesMapping as the authorities would have a OIDCUserService type mapping which the latest one doesnt. However if one uses DB to populate the roles to the Granted Authorities then they can still go with this option and not the OidcUser option.This is working as of now.