You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot/Vaadin/Kotlin集成Keycloak:代码配置下GrantedAuthoritiesMapper失效

解决方案:代码配置Keycloak后GrantedAuthoritiesMapper不生效问题

问题核心在于手动配置ClientRegistrationRepository时,Spring不会自动关联自定义的GrantedAuthoritiesMapper,需要在SecurityFilterChain中显式将其绑定到OAuth2用户服务里。结合Vaadin的安全机制,按以下步骤修复:

1. 确保自定义GrantedAuthoritiesMapper的Bean定义正确

先确认权限映射类被Spring容器管理,示例代码:

import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Component

@Component
class KeycloakAuthoritiesMapper : GrantedAuthoritiesMapper {
    override fun mapAuthorities(authorities: Collection<GrantedAuthority>): Collection<GrantedAuthority> {
        // 从Keycloak的realm_access.claims中提取角色,转为Spring标准的ROLE_前缀格式
        val oidcUser = authorities.filterIsInstance<OidcUser>().firstOrNull() ?: return authorities
        return oidcUser.claims["realm_access"]?.let { realmAccess ->
            (realmAccess as Map<*, *>)["roles"]?.let { roles ->
                (roles as Collection<*>).map { role ->
                    SimpleGrantedAuthority("ROLE_$role")
                }
            } ?: emptyList()
        }?.plus(authorities) ?: authorities
    }
}

2. 在SecurityFilterChain中绑定权限映射器到OAuth2用户服务

因为使用Vaadin,需要继承VaadinWebSecurity,同时显式配置OAuth2登录的用户信息端点,注入自定义映射器:

import com.vaadin.flow.spring.security.VaadinWebSecurity
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
import org.springframework.security.oauth2.core.oidc.user.OidcUser

@Configuration
@EnableWebSecurity
class SecurityConfig(
    private val authoritiesMapper: KeycloakAuthoritiesMapper,
    private val clientRegistrationRepository: ClientRegistrationRepository
) : VaadinWebSecurity() {

    override fun configure(http: HttpSecurity) {
        super.configure(http)
        // 配置OAuth2登录,关联自定义的ClientRegistrationRepository
        http.oauth2Login { oauth2 ->
            oauth2.clientRegistrationRepository(clientRegistrationRepository)
            oauth2.userInfoEndpoint { userInfo ->
                userInfo.oidcUserService(customOidcUserService())
            }
        }
        // 设置Vaadin的登录跳转路径为Keycloak授权端点
        setLoginView("/oauth2/authorization/keycloak")
    }

    // 自定义OidcUserService,手动应用权限映射逻辑
    private fun customOidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
        val defaultUserService = OidcUserService()
        return OAuth2UserService { request, user ->
            val loadedUser = defaultUserService.loadUser(request)
            // 调用自定义映射器替换原有权限集合
            val mappedAuthorities = authoritiesMapper.mapAuthorities(loadedUser.authorities)
            loadedUser.authorities = mappedAuthorities.toMutableSet()
            loadedUser
        }
    }
}

3. 关键检查点

  • ClientRegistration的Scope配置:必须包含openid和roles(或Keycloak返回角色所需的scope),示例:
    @Bean
    fun clientRegistrationRepository(): ClientRegistrationRepository {
        return InMemoryClientRegistrationRepository(
            ClientRegistration.withRegistrationId("keycloak")
                .clientId("your-client-id")
                .clientSecret("your-client-secret")
                .authorizationUri("http://localhost:8080/realms/your-realm/protocol/openid-connect/auth")
                .tokenUri("http://localhost:8080/realms/your-realm/protocol/openid-connect/token")
                .userInfoUri("http://localhost:8080/realms/your-realm/protocol/openid-connect/userinfo")
                .jwkSetUri("http://localhost:8080/realms/your-realm/protocol/openid-connect/certs")
                .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
                .scope("openid", "profile", "roles") // 必须包含roles才能获取角色信息
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .clientName("Keycloak")
                .build()
        )
    }
    
  • Vaadin视图权限控制:确保页面用@Secured("ROLE_your-role")或@PreAuthorize("hasRole('your-role')")正确标注
  • Spring组件扫描:确认KeycloakAuthoritiesMapper所在包被@ComponentScan覆盖

内容的提问来源于stack exchange,提问作者Frank Marx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:05:03