Интеграция Spring Security Global Method Security с помощью Keycloak - PullRequest
1 голос
/ 24 апреля 2019

У меня проблемы с использованием аннотаций до / после авторизации Spring Security и Servlet API с интеграцией Keycloak. Я исследовал множество статей, учебных пособий и следующих вопросов без дальнейшей удачи :

Все, что мне нужно, это удалить префикс ROLES_ , использовать иерархические роли и удобный способ получения ролей пользователей.

На данный момент я могу получить иерархическую роль, подобную этой, в контроллере, но не могу использовать аннотации:

@Controller
class HomeController {

    @Autowired
    AccessToken token

    @GetMapping('/')
    def home(Authentication auth, HttpServletRequest request) {
        // Role 'admin' is defined in Keycloak for this application
        assert token.getResourceAccess('my-app').roles == ['admin']
        // All effective roles are mapped
        assert auth.authorities.collect { it.authority }.containsAll(['admin', 'author', 'user'])

        // (!) But this won't work:
        assert request.isUserInRole('admin')
    }

    // (!) Leads to a 403: Forbidden
    @GetMapping('/sec')
    @PreAuthorize("hasRole('admin')") {
        return "Hello World"
    }

}

Я предполагаю, что аннотация @PreAuthorize не работает, потому что этот метод сервлета не успешен.

В Keycloak и Spring определены только три роли - администратор, автор, пользователь:

enum Role {
    USER('user'),
    AUTHOR('author'),
    ADMIN('admin')

    final String id

    Role(String id) {
        this.id = id
    }

    @Override
    String toString() {
        id
    }
}

Конфигурация Keycloak

При удалении аннотации @EnableGlobalMethodSecurity из этой веб-безопасности выявляется Error creating bean with name 'resourceHandlerMapping', вызванная ошибкой No ServletContext set - без понятия, откуда она взялась!

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(keycloakAuthenticationProvider().tap { provider ->
            // Assigns the Roles via Keycloaks role mapping
            provider.grantedAuthoritiesMapper = userAuthoritiesMapper
        })
    }

    @Bean
    RoleHierarchyImpl getRoleHierarchy() {
        new RoleHierarchyImpl().tap {
            hierarchy = "$Role.ADMIN > $Role.AUTHOR > $Role.USER"
        }
    }

    @Bean
    GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
        new RoleHierarchyAuthoritiesMapper(roleHierarchy)
    }

    SecurityExpressionHandler<FilterInvocation> expressionHandler() {
        // Removes the prefix
        new DefaultWebSecurityExpressionHandler().tap {
            roleHierarchy = roleHierarchy
            defaultRolePrefix = null
        }
    }

    // ...

    @Bean
    @Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    AccessToken accessToken() {
        def request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
        def authToken = (KeycloakAuthenticationToken) request.userPrincipal
        def securityContext = (KeycloakSecurityContext) authToken.credentials

        return securityContext.token
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http)
        http
            .authorizeRequests()
            .expressionHandler(expressionHandler())
            // ...
    }

}

Глобальная настройка безопасности метода

Мне нужно было явно разрешить allow-bean-definition-overriding, потому что в противном случае я получил ошибку bean with that name already defined, которая показывает, что я полностью потерял контроль над всей этой ситуацией и не знаю, что происходит.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    @Autowired
    RoleHierarchy roleHierarchy

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        ((DefaultMethodSecurityExpressionHandler)super.createExpressionHandler()).tap {
            roleHierarchy = roleHierarchy
            defaultRolePrefix = null
        }
    }
}

Какие еще конфигурации могут быть важны? Большое спасибо за вашу помощь!

1 Ответ

0 голосов
/ 25 апреля 2019

Как М.Деин указал, что нужно удалить defaultRolePrefix в нескольких местах с помощью BeanPostProcessor, что объясняется в (docs.spring.io) Отключить префикс ROLE_ .

Этот подход показался мне не очень чистым, и поэтому я написал пользовательский AuthoritiesMapper для достижения сопоставления иерархических ролей из Keycloak без необходимости переименовывать их в стандарт ROLE_ Spring.Во-первых, перечисление Roles было изменено для соответствия этому стандарту внутри области приложения:

enum Role {
    USER('ROLE_USER'),
    AUTHOR('ROLE_AUTHOR'),
    ADMIN('ROLE_ADMIN')

    // ...
}

Во-вторых, я заменил RoleHierarchyAuthoritiesMapper на иерархическую реализацию с префиксом:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    // ..

    // Replaces the RoleHierarchyAuthoritiesMapper
    @Bean
    GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
        new PrefixingRoleHierarchyAuthoritiesMapper(roleHierarchy)
    }

}
class PrefixingRoleHierarchyAuthoritiesMapper extends RoleHierarchyAuthoritiesMapper {

        String prefix = 'ROLE_'

        PrefixingRoleHierarchyAuthoritiesMapper(RoleHierarchy roleHierarchy) {
            super(roleHierarchy)
        }

        @Override
        Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
            def prefixedAuthorities = authorities.collect { GrantedAuthority originalAuthority ->
                new GrantedAuthority() {
                    String authority = "${prefix}${originalAuthority.authority}".toUpperCase()
                }
            }

            super.mapAuthorities(prefixedAuthorities)
        }
    }

И, наконец, я избавился от GlobalMethodSecurityConfig.

...