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

如何通过Keycloak认证直接访问带角色权限的端点?

问题分析与解决方案

你的核心问题是混淆了Spring Security中**OAuth2资源服务器(JWT模式)和OAuth2登录(会话模式)**的工作逻辑:

  1. 两种模式的差异

    • 资源服务器模式(oauth2ResourceServer().jwt()):依赖请求头中的Authorization: Bearer <token>完成认证和权限校验,适合Postman、Swagger这类手动携带令牌的场景。
    • 会话登录模式(oauth2Login()):浏览器完成Keycloak登录后,服务器通过会话Cookie维护认证状态,请求时自动携带Cookie而非Authorization头,适合前端页面直接访问后端接口的场景。
  2. 当前配置的冲突点
    你同时启用了两种模式,但角色校验hasAuthority(requiredRoleName)仅针对JWT转换的权限生效。当浏览器通过会话登录时,Spring Security生成的OAuth2AuthenticationToken并没有包含Keycloak的角色信息,导致权限校验失败;而去掉角色校验后,会话认证本身有效,所以能正常访问接口。


修正方案

方案一:支持浏览器会话登录(自动用Cookie认证)

需要配置oauth2Login的用户权限转换器,将Keycloak返回的角色转换为Spring Security可识别的GrantedAuthority:

  1. 新增OAuth2用户服务Bean:
@Bean
public OAuth2UserService<OidcUserRequest, OidcUser> oauth2UserService() {
    final OidcUserService delegate = new OidcUserService();
    return userRequest -> {
        // 加载Keycloak返回的用户信息
        OidcUser oidcUser = delegate.loadUser(userRequest);
        // 提取realm中的角色
        Map<String, Object> realmAccess = oidcUser.getClaimAsMap("realm_access");
        Collection<String> roles = (Collection<String>) realmAccess.getOrDefault("roles", Collections.emptyList());
        // 将角色转换为GrantedAuthority
        List<GrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        // 返回包含权限的OIDC用户对象
        return new DefaultOidcUser(authorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
    };
}
  1. 在filterChain中关联该服务:
http.oauth2Login()
        .userService(oauth2UserService()) // 新增这一行
        .and()
        .logout()
        .addLogoutHandler(keycloakLogoutHandler)
        .logoutSuccessUrl("/");
  1. 调整权限校验的顺序(避免/**覆盖后续规则):
http.authorizeRequests(auth -> {
    auth.requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").authenticated();
    auth.anyRequest().hasAuthority(requiredRoleName); // 替换原有的/**规则
});

方案二:仅使用资源服务器模式(必须携带Authorization头)

如果不需要浏览器会话登录,仅支持手动携带令牌的场景,可以移除oauth2Login()相关配置,浏览器访问时需通过插件(如ModHeader)手动添加Authorization: Bearer <token>请求头。


补充优化

你的CorsFilter当前是空实现,建议补充跨域规则(生产环境请指定具体域名而非*):

@Bean
CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(Collections.singletonList("*"));
    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 07:37:50