javaspring-security

Spring Security Authentication returns empty authorities


I'm trying to implement spring security for the first time, I'm using the following maven dependency:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

My UserDetailsService implementation:

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserInboundPort, UserDetailsService {
    
    private final UserOutboundPort userOutboundPort;

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserRole userRole = userOutboundPort.findUserRoleByEmail(username);
        User user = userRole.getUser();
        Role role = userRole.getRole();
        
        Collection<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(role.getCode())); // <- role.getCode() returns "ROLE_ADMIN"
        
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
    }

}

My RestController contains methods with @PreAuthorize annotation that uses SecurityServiceImpl to provide access or not

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/account")
@ConditionalOnProperty(value="davinci.expose.rest", havingValue = "true")
public class AccountRestController {
    
    private final AccountInboundAdapterRestImpl accountInboundAdapterRestImpl;

    @PreAuthorize("@securityServiceImpl.isAdminOrUserWithPermission(#id)")
    @GetMapping(value = "/id/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public DavinciApiResponse<Account> findById(@PathVariable("id") Long id) {
        return accountInboundAdapterRestImpl.findById(id);
    }

}

SecurityServiceImpl:

@Service
@ConditionalOnProperty(name = "davinci.security.enabled", havingValue = "true")
@RequiredArgsConstructor
public class SecurityServiceImpl implements SecurityInboundPort {
    
    private final AccountInboundPort accountInboundPort;
    
    @Override
    public boolean isAdminOrUserWithPermission(Long id) {
        // Get the current authenticated user
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !authentication.isAuthenticated()) {
            // No user is authenticated
            return false;
        }

        // Check if the user has the "ADMIN" role

        // THIS IS NOT WORKING !!! authentication.getAuthorities() return empty list
//      boolean isAdmin = authentication.getAuthorities().stream()
//              .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ADMIN"));
        
        // THIS IS WORKING !
        final Jwt principal = (Jwt) authentication.getPrincipal();
        final String claimAuthorities = (String) principal.getClaim("authorities");
        boolean isAdmin = claimAuthorities.equals("ADMIN");
        
        // If the user is an admin, return true
        if (isAdmin) {
            return true;
        }

        // Get the authenticated user's email
        String userEmail = authentication.getName();
        
        Account account = accountInboundPort.findByEmail(userEmail);

        // Return true if the accountId matches the id of the account of the authenticated user
        return account.getId().equals(id);
    }

    @Override
    public String getAuthenticatedUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication.getName();
    }

}

why authentication.getAuthorities() is empty ? Am I missing something ?


Solution

  • If using the built in resource server implementation that comes with spring security Spring Security will automatically try to extract authorities from the scope claim in the provided JWT.

    Not all authentication servers provide authorities in the scope claim, so you can provide a JwtAuthenticationConverter of type JwtGrantedAuthoritiesConverter that comes built in to tell Spring Security how to construct granted authorities.

    It is documented here Extracting Authorities Manually and the docs provide a simple example.

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
    
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
    

    if you wish to see a fully working example, with a proper tutorial of implementing a resource server that handles JWTs there is one here and here is the code for the tutorial that includes a fully well commented implementation.