web-dev-qa-db-fra.com

Activer l'authentification des rôles avec Spring Boot (sécurité) et Keycloak?

J'essaie de faire une chose simple.

Je veux faire une demande à un seul point de terminaison et envoyer un jeton au porteur (à partir d'un client), je veux que ce jeton soit validé et en fonction du rôle attribué sur la demande d'acceptation/refus de keycloak sur mon point de terminaison.

J'ai suivi de nombreux tutoriels et même des livres, mais surtout je ne comprends tout simplement pas.

Suivi de cela pour configurer mes informations de capuchon (domaine, rôle, utilisateur) https://medium.com/@bcarunmail/securing-rest-api-using-keycloak-and-spring-oauth2-6ddf3a1efcc2

Alors,

J'ai essentiellement mis en place mon keycloak avec un client, un utilisateur avec un rôle spécifique "utilisateur" et l'ai configuré comme ceci:

@Configuration
@KeycloakConfiguration
//@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConf extends KeycloakWebSecurityConfigurerAdapter
{
    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(keycloakAuthenticationProvider());
    }

    /**
     * Defines the session authentication strategy.
     */
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
            KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
            KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        super.configure(http);
        http
                .authorizeRequests()
                .antMatchers("/user/*").hasRole("admin")
                .antMatchers("/admin*").hasRole("user")

    }
}

Je ne comprends pas pourquoi dans de nombreux tutoriels je vois cela (comme la dernière règle):

.anyRequest().permitAll();

Fondamentalement, lorsque je définis que je n'ai aucune sécurité, je peux appeler les points de terminaison sans jeton de support.

Mais quand j'ajoute cela comme dernière règle

 .anyRequest().denyAll();

J'ai toujours un 403.

Débogage J'ai trouvé ceci:

La demande consiste à traiter l'authentification

f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
o.k.a.BearerTokenRequestAuthenticator    : Found [1] values in authorization header, selecting the first value for Bearer.
o.k.a.BearerTokenRequestAuthenticator    : Verifying access_token
o.k.a.BearerTokenRequestAuthenticator    : successful authorized
a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: [] 
o.k.adapters.RequestAuthenticator        : User 'testuser' invoking 'http://localhost:9090/api/user/123' on client 'users'
o.k.adapters.RequestAuthenticator        : Bearer AUTHENTICATED
f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
o.s.s.authentication.ProviderManager     : Authentication attempt using org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider
o.s.s.core.session.SessionRegistryImpl   : Registering session 5B871A0E2AF55B70DC8E3B7436D79333, for principal testuser
f.KeycloakAuthenticationProcessingFilter : Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken@355f68d6: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount@5d7a32a9; Not granted any authorities
[nio-9090-exec-3] o.s.security.web.FilterChainProxy        : /api/user/123 at position 8 of 15 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
nio-9090-exec-3] o.s.s.w.s.DefaultSavedRequest            : pathInfo: both null (property equals)
[nio-9090-exec-3] o.s.s.w.s.DefaultSavedRequest            : queryString: both null (property equals)

On dirait que je n'obtiens aucun rôle de porteur ...

Mes dépendances:

        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>6.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
            <version>6.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

Mon problème?

Je demande l'envoi d'un jeton d'accès:

client_id -> my client from keycloak
username -> my user from keycloak
password -> my password from keycloak
grant_type -> password
client_secret -> from keycloak

J'obtiens un jeton, puis j'utilise pour demander à mon endpoint d'application. Mes demandes sont toujours valides quel que soit le point de terminaison que j'utilise (celui avec l'utilisateur de rôle ou avec l'administrateur de rôle).

Dans mes propriétés, j'ai quelque chose comme ça:

keycloak:
  auth-server-url: http://localhost:8080/auth/
  resource: users-api
  credentials:
    secret : my-secret
  use-resource-role-mappings : true
  realm: my-realm
  realmKey:  my-key
  public-client: true
  principal-attribute: preferred_username
  bearer-only: true

Une idée de comment activer réellement les rôles dans ce cas?

Dois-je configurer un client pour utiliser JWT? des idées?

J'ai également ajouté les annotations sur mon point de terminaison

@Secured("admin")
@PreAuthorize("hasAnyAuthority('admin')")

mais ils ne font rien ...

-- ÉDITER --

Après avoir corrigé l'URL pour correspondre à la ressource, j'obtiens toujours 403.

"realm_access": {
    "roles": [
      "offline_access",
      "admin",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },

Est-il en quelque sorte lié le resource_access à mon problème?

13
jpganz18
  1. Essayez-vous sans le @Configuration? Je pense que vous avez seulement besoin de @KeycloakConfiguration annotation sur votre classe SecurityConf.

  2. Vos antMatchers respectent-ils la casse?

 http
    .csrf().disable()
    .authorizeRequests()
    .antMatchers("/api/user/**").hasRole("user")
    .antMatchers("/api/admin/**").hasRole("admin")
    .anyRequest().authenticated();
  1. Veuillez également essayer cette configuration, pour supprimer les conventions ROLE_ * définies par Java:
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        // SimpleAuthorityMapper is used to remove the ROLE_* conventions defined by Java so
        // we can use only admin or user instead of ROLE_ADMIN and ROLE_USER
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
  1. Si tous vos points de terminaison ont les mêmes logiques, la configuration de sécurité devrait être suffisante, vous n'avez pas besoin d'autres annotations. Mais si vous avez un autre point de terminaison avec le rôle d'administrateur, qui n'est pas dans votre contrôleur "/ api/admin", vous pouvez essayer:
@PreAuthorize("hasRole('admin')")
0
bamandine