SpringBoot/Vaadin/Kotlin集成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
相关产品推荐
相关产品推荐

