Включить аутентификацию ролей с помощью весенней загрузки (безопасность) и keycloak?

Я пытаюсь сделать простую вещь.

Хочу сделать запрос к одной конечной точке и отправить токен на предъявителя (от клиента), я хочу, чтобы этот токен был проверен и в зависимости от роли, назначенной в запросе приема/отклонения keycloak на моей конечной точке.

Я следовал многим учебникам и даже книгам, но большинство из них я просто не понимаю.

Следуйте этому, чтобы настроить информацию о моем ключевом плаще (область, роль, пользователь) https://medium.com/@bcarunmail/securing-rest-api-using-keycloak-and-spring-oauth2-6ddf3a1efcc2

Так,

Я в основном настроил свой keycloak с клиентом, пользователем с определенной ролью "пользователь", и настроил его так:

@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")

    }
}

Я не понимаю, почему во многих уроках я вижу это (как последнее правило):

.anyRequest().permitAll();

По сути, когда я устанавливаю, что у меня нет защиты, я могу вызывать конечные точки без токена на предъявителя.

Но когда я добавляю это как последнее правило

 .anyRequest().denyAll();

Я всегда получаю 403.

Отладка я нашел это:

Запрос на обработку аутентификации

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.key[email protected]355f68d6: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: org[email protected]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)

Похоже, я не получаю роли на предъявителя...

Мои зависимости:

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

Моя проблема?

Я запрашиваю отправку токена доступа:

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

Я получаю токен и затем использую для запроса к своему приложению endoint. Мои запросы всегда действительны, независимо от того, какую конечную точку я использую (ту, в которой есть пользователь роли или администратор роли).

В моих свойствах у меня есть что-то вроде этого:

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

Есть идеи, как на самом деле включить роли в этом случае?

Должен ли я настроить клиент для использования JWT? есть идеи?

Я также добавил аннотации к моей конечной точке

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

но, кажется, они ничего не делают...

-- РЕДАКТИРОВАТЬ --

После исправления URL, чтобы соответствовать ресурсу, я все равно получаю 403.

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

Это как-то связано resource_access с моей проблемой?

Ответ 1

в стеке отладки: я вижу, что вы звоните /api/user/123 и в ваших настройках безопасности вы защищаете /user/* что не то же самое, измените вашу безопасность на:

.antMatchers("/api/user/*").hasRole("user")
                .antMatchers("/api/admin*").hasRole("admin")

PS: вам не нужно регистрировать KeycloakAuthenticationProcessingFilter и KeycloakPreAuthActionsFilter

Ответ 2

permitAll:

Всякий раз, когда вы хотите разрешить любому запросу доступ к конкретному ресурсу /URL, вы можете использовать allowAll. Например, URL-адрес для входа должен быть доступен каждому.

denyAll:

Всякий раз, когда вы хотите заблокировать доступ к определенному URL, независимо от того, откуда поступает запрос или кто делает запрос (ADMIN)

У вас также есть несоответствие URL-адреса и роли (вы предоставляете URL-адрес с правами администратора для пользователя и наоборот). (Хорошей практикой является использование роли ROLE_ADMIN, ADMIN или USER.) Сформируйте свой стек, который я вижу. Не предоставлено никаких прав доступа, поэтому, пожалуйста, перепроверьте код с правами доступа.

 http
         .csrf().disable()
         .authorizeRequests()
         .antMatchers("/api/user/**").hasRole("ADMIN")
         .antMatchers("/api/admin/**").hasRole("USER")
         .anyRequest().authenticated();

Ответ 3

  1. Вы пытаетесь без @Configuration? Я думаю, что вам нужна @KeycloakConfiguration аннотация @KeycloakConfiguration в вашем классе SecurityConf.

  2. Уважают ли ваши antMatchers чувствительность к регистру?

 http
    .csrf().disable()
    .authorizeRequests()
    .antMatchers("/api/user/**").hasRole("user")
    .antMatchers("/api/admin/**").hasRole("admin")
    .anyRequest().authenticated();
  1. Также попробуйте эту конфигурацию, чтобы удалить соглашения ROLE_ *, определенные 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. Если все ваши конечные точки имеют одинаковую логику, конфигурации безопасности должно быть достаточно, другие аннотации не нужны. Но если у вас есть другая конечная точка с ролью администратора, которой нет в вашем контроллере "/api/admin", вы можете попробовать:
@PreAuthorize("hasRole('admin')")