如何通过Keycloak认证直接访问带角色权限的端点?
问题分析与解决方案
你的核心问题是混淆了Spring Security中**OAuth2资源服务器(JWT模式)和OAuth2登录(会话模式)**的工作逻辑:
两种模式的差异
- 资源服务器模式(
oauth2ResourceServer().jwt()):依赖请求头中的Authorization: Bearer <token>完成认证和权限校验,适合Postman、Swagger这类手动携带令牌的场景。 - 会话登录模式(
oauth2Login()):浏览器完成Keycloak登录后,服务器通过会话Cookie维护认证状态,请求时自动携带Cookie而非Authorization头,适合前端页面直接访问后端接口的场景。
- 资源服务器模式(
当前配置的冲突点
你同时启用了两种模式,但角色校验hasAuthority(requiredRoleName)仅针对JWT转换的权限生效。当浏览器通过会话登录时,Spring Security生成的OAuth2AuthenticationToken并没有包含Keycloak的角色信息,导致权限校验失败;而去掉角色校验后,会话认证本身有效,所以能正常访问接口。
修正方案
方案一:支持浏览器会话登录(自动用Cookie认证)
需要配置oauth2Login的用户权限转换器,将Keycloak返回的角色转换为Spring Security可识别的GrantedAuthority:
- 新增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()); }; }
- 在
filterChain中关联该服务:
http.oauth2Login() .userService(oauth2UserService()) // 新增这一行 .and() .logout() .addLogoutHandler(keycloakLogoutHandler) .logoutSuccessUrl("/");
- 调整权限校验的顺序(避免
/**覆盖后续规则):
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
相关产品推荐
相关产品推荐

