2

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?

sand87
  • 123
  • 1
  • 11

2 Answers2

1

@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.

sand87
  • 123
  • 1
  • 11
0

If you want to project your application with app role, please refer to the following steps

The example is used app role auth to project web api

  1. Register web API application and configure API scope

  2. Define app role in your Azure AD web api application. Please add the following content in your application manifest

  "appRoles": [
    {
      "allowedMemberTypes": [
        "User"
      ],
      "displayName": "Admin",
      "id": "2fa848d0-8054-4e11-8c73-7af5f1171001",
      "isEnabled": true,
      "description": "Full admin access",
      "value": "Admin"
    },
    {
      "allowedMemberTypes": [
        "User"
      ],
      "displayName": "User",
      "id": "f8ed78b5-fabc-488e-968b-baa48a570001",
      "isEnabled": true,
      "description": "Normal user access",
      "value": "User"
    }
  ],
  1. Assign these roles for the user

  2. Register client application in Azure AD and configure API permissions

  3. Enable implicit flow in client application

  4. Configure API application

a. sdk

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

b. application.properties

azure.activedirectory.session-stateless=true
azure.activedirectory.client-id=xxxxxx-your-client-id-xxxxxx
azure.activedirectory.appIdUri=xxxxxx-your-appIDUri-xxxxxx

c. WebSecurityConfig class

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AADAppRoleStatelessAuthenticationFilter aadAuthFilter;

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

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);

        http.authorizeRequests()
            .antMatchers("/", "/index.html", "/public").permitAll()
            .anyRequest().authenticated();

        http.addFilterBefore(aadAuthFilter, UsernamePasswordAuthenticationFilter.class);

    }
}

d. Controller

@RestController
public class HelloController {

    @GetMapping("/public")
    @ResponseBody
    public String publicMethod() {
        return "public endpoint response";
    }

    @GetMapping("/authorized")
    @ResponseBody
    @PreAuthorize("hasAnyRole('User','Admin')")
    public String onlyAuthorizedUsers() {

        return "authorized endpoint response";
    }

    @GetMapping("/admin/demo")
    @PreAuthorize("hasRole('Admin')")
    @ResponseBody
    public String onlyForAdmins() {
        return "admin endpoint";
    }
}
  1. Test. I use a single page to do a test

enter image description here enter image description here

For more details, please refer to here and here

Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • Thanks JIm.This is what i have currently done with a few changes to the code to be able to use the roles.What i was looking to was setting the roles in AD premium which adds it on a directory level.However in that case am not getting anything with respect to the custom roles i added in AD premium in the token. – sand87 Jul 15 '20 at 15:11
  • @sand87 What do you mean custom role? Is that your use Azure AD Custom administrator roles : https://learn.microsoft.com/en-us/azure/active-directory/users-groups-roles/roles-custom-overview? – Jim Xu Jul 16 '20 at 01:11
  • Yes this one https://learn.microsoft.com/en-us/azure/active-directory/users-groups-roles/roles-create-custom – sand87 Jul 19 '20 at 16:43
  • @sand87 If you want to use custom directory role to do auth, it is impossible. Because we have a way to store the information in the token. For more details, please refer to https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens – Jim Xu Jul 20 '20 at 07:21
  • Besides, in Azure AD, the role based auth means that using app role to do auth. Could you please tell me why you use the way to do auth? – Jim Xu Jul 20 '20 at 07:22
  • the reason was for manifest we have to go in there manually and make changes,if in future the client wants more roles.However if its AD premium i guess they have graph endpoints that can be used in the respt application to call and create a role by the admin himself/herself without having to go into portal and do this stuff. – sand87 Jul 20 '20 at 07:36
  • @sand87 Is that you want to use Microsoft graph to create app role in your application? – Jim Xu Jul 20 '20 at 07:43
  • @sand87 Could you please describe your need in detail? – Jim Xu Jul 20 '20 at 08:05
  • yes sort of.i wanted to have the roles in Azure AD premium so that we can use apis to create roles,In the workaround that you suggested and i have also used i need to manually go into portal and introduce roles whenever they are needed.I wanted to overcome that – sand87 Jul 29 '20 at 07:00
  • @sand87s You can use Microsoft graph to create role – Jim Xu Jul 29 '20 at 07:21