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

使用Postman访问Spring Boot接口时Keycloak认证失败问题排查

问题解决:Postman携带Access Token访问Spring Boot接口返回认证页面

你的问题核心是当前Security配置仅支持浏览器端的OAuth2授权码流程(oauth2Login),未配置资源服务器模式来处理请求头中的Bearer Token。Postman直接携带Access Token的场景属于资源服务器模式,需要补充以下配置:

1. 补充资源服务器配置

在SecurityFilterChain中添加oauth2ResourceServer()配置,同时自定义JWT权限转换逻辑,确保和登录场景的权限提取规则一致:

@Configuration
@EnableWebSecurity
public class KeycloakSecurityConfig {

    @Value("${eval.required.role.name}")
    private String requiredRoleName;
    
    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;

    @Bean
    CorsFilter corsFilter() {
        CorsFilter filter = new CorsFilter();
        return filter;
    }

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http, KeycloakLogoutHandler keycloakLogoutHandler) throws Exception {

        http.addFilterBefore(corsFilter(), SessionManagementFilter.class);

        http.authorizeRequests()
                .antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/logout").authenticated()
                .anyRequest().hasAuthority(requiredRoleName);

        // 同时支持浏览器登录和资源服务器Token认证
        http.oauth2Login(Customizer.withDefaults())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt
                        .jwkSetUri(jwkSetUri)
                        .jwtAuthenticationConverter(jwtAuthenticationConverter())))
                .logout()
                .addLogoutHandler(keycloakLogoutHandler)
                .logoutSuccessUrl("/");

        return http.build();
    }

    // 自定义JWT权限转换器,从Token的"Roles"Claim中提取权限
    @Bean
    JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(jwt -> {
            List<String> roles = jwt.getClaim("Roles");
            if (roles == null) {
                return Collections.emptyList();
            }
            return roles.stream()
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
        });
        return converter;
    }


    @Component
    @RequiredArgsConstructor
    static class GrantedAuthoritiesMapperImpl implements GrantedAuthoritiesMapper {

        @Override
        public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                if (OidcUserAuthority.class.isInstance(authority)) {
                    final var oidcUserAuthority = (OidcUserAuthority) authority;
                    mappedAuthorities.addAll(extractAuthorities(oidcUserAuthority.getIdToken().getClaims()));

                } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                    try {
                        final var oauth2UserAuthority = (OAuth2UserAuthority) authority;
                        final var userAttributes = oauth2UserAuthority.getAttributes();
                        mappedAuthorities.addAll(extractAuthorities(userAttributes));

                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            });

            return mappedAuthorities;
        }

        @SuppressWarnings({"rawtypes", "unchecked"})
        private static Collection<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {
            if (claims != null) {
                ArrayList<String> roles = (ArrayList<String>) claims.get("Roles");
                List<GrantedAuthority> authorities = new ArrayList<>(roles.size());
                for (String role : roles) {
                    authorities.add(new SimpleGrantedAuthority(role));
                }

                return authorities;
            }
            return Collections.emptyList();
        }
    }
}

2. 配置Keycloak JWK地址

在application.properties(或application.yml)中添加Keycloak的公钥获取地址,用于验证JWT签名:

spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://你的Keycloak地址/auth/realms/你的领域/protocol/openid-connect/certs

关键修改说明

  • oauth2ResourceServer():启用资源服务器模式,让Spring Security识别并处理请求头中的Authorization: Bearer {token}。
  • jwtAuthenticationConverter():自定义权限转换逻辑,和登录场景的extractAuthorities逻辑对齐,确保从JWT的RolesClaim中提取角色权限。
  • JWK地址配置:Spring Security需要通过该地址获取Keycloak的公钥,验证Token的合法性。

完成以上修改后,Postman携带Access Token访问接口时,Spring Security会直接解析Token并验证权限,不再重定向到认证页面。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:37:38