将Spring Security全局方法安全性与Keycloak集成

时间:2019-04-24 13:02:23

标签: spring spring-boot groovy spring-security keycloak

我在使用Spring Security的Pre / Post授权注释和带有Keycloak集成的Servlet API时遇到问题。我调查了许多文章,教程和以下问题,没有运气

我要删除的是 ROLES _ 前缀,使用分层角色和一种便捷的方式来检索用户的角色。

截至目前,我已经能够在Controller中检索这样的分层角色,但是不能使用注释:

@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注释不起作用,因为该Servlet方法不成功。

在Keycloak和Spring中只有三个角色-管理员,作者和用户:

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

    final String id

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

    @Override
    String toString() {
        id
    }
}

密钥斗篷配置

从此网络安全性中删除@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
        }
    }
}

还有其他重要的配置吗?非常感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

除了 (docs.spring.io) Disable ROLE_ Prefixing 提供的建议和 M. Deinum 提供的建议外,使用 KeycloakWebSecurityConfigurerAdapter 时还需要进行一项修改。

在 configureGlobal 方法中,grantedAuthoritiesMapper bean 在 bean keycloakAuthenticationProvider 中设置。在 grantAuthoritiesMapper 中,前缀可以设置为任何你想要的,默认值为“ROLE_”。

代码如下:

    @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();

    SimpleAuthorityMapper grantedAuthoritiesMapper = new SimpleAuthorityMapper();
    grantedAuthoritiesMapper.setPrefix("");
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper);
    auth.authenticationProvider(keycloakAuthenticationProvider);
}

这个解决方案对我有用。

答案 1 :(得分:0)

正如M. Deinum所指出的,必须用defaultRolePrefix在多个位置删除BeanPostProcessor,这在(docs.spring.io) Disable ROLE_ Prefixing中有解释。

这种方法对我来说似乎不是很干净,因此我编写了一个自定义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