'Spring Security OAuth2 - No Granted Authorities after authorization on Custom Auth Server

I have User with authorities USER and ADMIN. So after authorization on Auth Server I need to get my User granted authorities + Scopes (ROLE_USER, ROLE_ADMIN, SCOPE_openid). But I always have only ROLE_USER and SCOPE_openid without authority ROLE_ADMIN, which I need.

How can I get all user's granted authorities in Client after authorization?

My Custom UserDetailsService (on Auth Server side). When I retrieve User from DB, I have all authorities (ADMIN, USER):

@Service
@AllArgsConstructor
public class ApplicationUserDetailsService implements UserDetailsService {

    private final ApplicationUserRepository applicationUserRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        ApplicationUser user = applicationUserRepository.findByEmail(email)
                .orElseThrow(()-> new UsernameNotFoundException(
                                String.format("User with email = '%s' not found", email)));
        return new User(
                user.getEmail(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                getAuthorities(user.getRoles())
        );
    }

    private Collection<? extends GrantedAuthority> getAuthorities(List<Role> roles) {
        return roles
                .stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }
}

Then in Client after successful Authorization on Auth Server I try to get all authorities from Authorization, but now I have the only authrority (USER):

@RestController
@RequestMapping("/api/v1/users")
public class ApplicationUserController {

    //Using WebClient due to OAuthRestTemplate is deprecated
    private final WebClient webClient;

    @GetMapping("/test")
    public ResponseEntity<String[]> getAllUsersString(
            @RegisteredOAuth2AuthorizedClient("api-client-authorization-code")
                    OAuth2AuthorizedClient client){
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(auth.isAuthenticated());
        System.out.println(auth.getAuthorities());
        System.out.println(auth.getPrincipal());
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(webClient
                    .get()
                    .uri("http://localhost:8090/api/v1/users/test")
                    .attributes(oauth2AuthorizedClient(client))
                    .retrieve()
                    .bodyToMono(String[].class)
                    .block()
                );
    }

OUTPUT:

true
[ROLE_USER, SCOPE_openid]
Name: [[email protected]], Granted Authorities: [[ROLE_USER, SCOPE_openid]], User Attributes: [{[email protected], aud=[api-client], azp=api-client, iss=http://localhost:9000, exp=2022-04-08T09:59:06Z, iat=2022-04-08T09:29:06Z, nonce=C7kk8irS0YzUtWVbwK78wTmQYjTBEh-tS59519K01DA}]

OUTPUT I need:

true
[ROLE_USER, ROLE_ADMIN, SCOPE_openid]
Name: [[email protected]], Granted Authorities: [[ROLE_USER,  ROLE_ADMIN, SCOPE_openid]], User Attributes: [{[email protected], aud=[api-client], azp=api-client, iss=http://localhost:9000, exp=2022-04-08T09:59:06Z, iat=2022-04-08T09:29:06Z, nonce=C7kk8irS0YzUtWVbwK78wTmQYjTBEh-tS59519K01DA}]

Client Security Config:

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

    private static final String[] WHITE_LIST_URLS = {
            "/hello",
            "/register",
            "/verifyRegistration*",
            "/resendVerifyToken*"
    };

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .authorizeHttpRequests()
                .antMatchers(WHITE_LIST_URLS).permitAll()
                .antMatchers("/api/**").authenticated()
                    .and()
                .oauth2Login()
                    .loginPage("/oauth2/authorization/api-client-oidc")
                    .and()
                .oauth2Client(Customizer.withDefaults());
    }
}

Client application.yml:

spring:
  security:
    oauth2:
      client:
        registration:
          api-client-oidc:
            provider: spring
            client-id: api-client
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/{registrationId}"
            scope: openid
            client-name: api-client-oidc
          api-client-authorization-code:
            provider: spring
            client-id: api-client
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8080/authorized"
            scope:
              - api.read
              - ROLE_ADMIN
              - ROLE_USER
            client-name: api-client-authorization-code
        provider:
          spring:
            issuer-uri: http://localhost:9000

Authorization Server Config:

AllArgsConstructor
@Configuration(proxyBeanMethods = false)
public class AuthenticationServerConfig {

    private final PasswordEncoder passwordEncoder;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(Customizer.withDefaults()).build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("api-client")
                .clientSecret(passwordEncoder.encode("secret"))
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/api-client-oidc")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("api.read")
                .scope("ROLE_ADMIN")
                .scope("ROLE_USER")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    private static RSAKey generateRsa() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .issuer("http://localhost:9000")
                .build();
    }
}

Can someone help me to solve this problem or maybe someone have similar example app OAuth2 + Spring Boot with User's Authorities?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source