0

I have this vanilla spring boot/azure/starter app, connecting to our internal azure service. https://learn.microsoft.com/de-de/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory

Generally it works as designed.

What options do i have if i want to add custom roles for authorization?

I want that flow:

  1. Login to azure with user/pw (works as expected)
  2. Load user“s roles from a local database (postgres)
  3. Inject/Add this roles into the list of spring's GrantedAuthority

With spring security we generally use a custom AuthenticationProvider

Currently i have this working code:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends AadWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login();

    }
}

I want something like this:

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class ThdAuthenticationProvider implements AuthenticationProvider {

    private final
    @NonNull
    IApplicationUserService userService;

    /**
     * Performs authentication with the same contract as .
     *
     * @param authentication the authentication request object.
     * @return a fully authenticated object including credentials. May return <code>null</code> if the
     * <code>AuthenticationProvider</code> is unable to support authentication of the passed
     * <code>Authentication</code> object. In such a case, the next <code>AuthenticationProvider</code> that
     * supports the presented <code>Authentication</code> class will be tried.
     * @throws AuthenticationException if authentication fails.
     */
    @Override
    public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication
                                                                                 authentication)
            throws AuthenticationException {
        final String name = authentication.getName().toLowerCase();
        final String password = authentication.getCredentials().toString();

        // go to azure, login with name/password
        // come back if sucessfull

        List<String> roles = userService.fetchRoles(name);
        
        List<GrantedAuthority> grantedAuth = new ArrayList<>();
        grantedAuth.addAll(roles);
        return new UsernamePasswordAuthenticationToken(name, password, grantedAuth);
}

EDIT

I ended up this way: Based on this documentation: https://docs.spring.io/spring-security/site/docs/5.2.12.RELEASE/reference/html/oauth2.html#oauth2login-advanced-map-authorities-oauth2userservice

My custom user service - where the roles will be fetched from database or elsewhere:

@Service
public class UserService {
    List<String> fetchUserRoles(String user){
        return List.of("Administrator", "Product Owner", "Developer");
    }
}

My custom security chain applying these roles:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends AadWebSecurityConfigurerAdapter {

    private final UserService userService;

    @Autowired
    public SecurityConfiguration(UserService userService) {
        this.userService = userService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .authorizeHttpRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login()
                .userInfoEndpoint(userInfoEndpointConfig -> {
                    userInfoEndpointConfig.oidcUserService(this.oidcUserService());
                });

    }

    private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();
        return (userRequest) -> {
            // Delegate to the default implementation for loading a user
            OidcUser oidcUser = delegate.loadUser(userRequest);

            OAuth2AccessToken accessToken = userRequest.getAccessToken();
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            // TODO
            // 1) Fetch the authority information from the protected resource using accessToken
            // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities

            // 3) Create a copy of oidcUser but use the mappedAuthorities instead

            List<String> dummy = userService.fetchUserRoles("dummy");
            dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
            oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());

            return oidcUser;
        };
    }
}
Thomas Lang
  • 1,285
  • 17
  • 35

2 Answers2

1

Spring Boot Azure AD custom roles

Please follow below link it has detail explanation about:

  • Register web API application and configure API scope

  • Assign these roles for the user

  • Register client application in Azure AD and configure API permissions

Reference:

Using Azure AD premium custom roles with spring security for role based access

B. B. Naga Sai Vamsi
  • 2,386
  • 2
  • 3
  • 11
0

@thomas-lang Thanks a lot Thomas!!! Your post helped me a lot!

Attaching my variation of the code

User Service

    @Service
    public class UserService {
    
        private final PeopleService peopleService;
    
        public UserService(PeopleService peopleService) {
            this.peopleService = peopleService;
        }
    
        public Set<Role> fetchUserRoles(String user, String email){
    
            Person loggedPerson = peopleService.findPersonByEmail(email);
    
            return loggedPerson.getRoles();
    
        }
    
    }

Security Configuration

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfigurationAad  extends AadWebSecurityConfigurerAdapter {
    
        private final UserService userService;
    
        @Autowired
        public SecurityConfigurationAad(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                    .authorizeHttpRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .oauth2Login()
                    .userInfoEndpoint(userInfoEndpointConfig -> {
                        userInfoEndpointConfig.oidcUserService(this.oidcUserService());
                    });
    
        }
    
        private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
            final OidcUserService delegate = new OidcUserService();
            return (userRequest) -> {
                // Delegate to the default implementation for loading a user
                OidcUser oidcUser = delegate.loadUser(userRequest);
    
                DecodedToken token = DecodedToken.getDecoded(userRequest.getAccessToken().getTokenValue());
    
                Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
    
                // TODO
                // 1) Fetch the authority information from the protected resource using accessToken
                // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
                // 3) Create a copy of oidcUser but use the mappedAuthorities instead
    
                Set<Role> dummy = userService.fetchUserRoles("dummy", token.unique_name);
                dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> String.valueOf(user)));
                oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
    
                return oidcUser;
            };
        }
    
    }

DecodedToken implementation I got from here
https://www.lenar.io/how-to-decode-jwt-authentication-token/

in3des
  • 31
  • 5