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

Spring Boot集成Keycloak:Postman JWT认证失败,仅支持页面登录

问题描述

参考Spring Boot集成Keycloak的示例实现功能,部署完成后:

  • 浏览器访问http://localhost:8081/customers会重定向到Keycloak登录页,输入凭据后API正常返回数据,符合预期
  • 用Postman获取JWT令牌后,以Bearer认证方式访问该接口,仍被重定向到Keycloak登录页

尝试过的操作:

  • 修改grant_type为client_credentials、password
  • 注释配置中的http.oauth2Login()
  • 使用已废弃的KeycloakWebSecurityConfigurerAdapter时可同时支持页面登录和JWT认证,但该方式不推荐

提出两个问题:

  1. 为何会出现这种情况?
  2. 如何配置才能同时支持两种认证方式?

应用调试日志如下:

12:11:49.133 [http-nio-8081-exec-2] DEBUG o.k.adapters.PreAuthActionsHandler - adminRequest http://localhost:8081/customers
12:11:49.137 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Using provider 'secret' for authentication of client 'login-app'
12:11:49.138 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider secret
12:11:49.139 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider jwt
12:11:49.139 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider secret-jwt
12:11:49.139 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider secret
12:11:49.139 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider jwt
12:11:49.139 [http-nio-8081-exec-2] DEBUG o.k.a.a.ClientCredentialsProviderUtils - Loaded clientCredentialsProvider secret-jwt
12:11:49.145 [http-nio-8081-exec-2] DEBUG o.k.a.t.AbstractAuthenticatedActionsValve - AuthenticatedActionsValve.invoke /customers
12:11:49.145 [http-nio-8081-exec-2] DEBUG o.k.a.AuthenticatedActionsHandler - AuthenticatedActionsValve.invoke http://localhost:8081/customers
12:11:49.145 [http-nio-8081-exec-2] DEBUG o.k.a.AuthenticatedActionsHandler - Policy enforcement is disabled.
12:11:49.147 [http-nio-8081-exec-2] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
12:11:49.147 [http-nio-8081-exec-2] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
12:11:49.147 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected StandardServletMultipartResolver
12:11:49.147 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected AcceptHeaderLocaleResolver
12:11:49.147 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected FixedThemeResolver
12:11:49.148 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@28b16193
12:11:49.148 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.support.SessionFlashMapManager@5d56c2d2
12:11:49.148 [http-nio-8081-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
12:11:49.148 [http-nio-8081-exec-2] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
12:11:49.155 [http-nio-8081-exec-2] DEBUG o.s.security.web.FilterChainProxy - Securing GET /customers
12:11:49.158 [http-nio-8081-exec-2] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
12:11:49.160 [http-nio-8081-exec-2] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
12:11:49.160 [http-nio-8081-exec-2] DEBUG o.s.s.w.s.SessionManagementFilter - Request requested invalid session id 2188C37ADF15140FB15235D80AAA0C77
12:11:49.165 [http-nio-8081-exec-2] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [GET /customers] with attributes [authenticated]
12:11:49.207 [http-nio-8081-exec-2] DEBUG o.s.s.w.s.HttpSessionRequestCache - Saved request http://localhost:8081/customers to session
12:11:49.207 [http-nio-8081-exec-2] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Trying to match using And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [And [Or [Ant [pattern='/login'], Ant [pattern='/favicon.ico']], And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.HeaderContentNegotiationStrategy@21fa9d8, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]
12:11:49.207 [http-nio-8081-exec-2] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@681b92a9
12:11:49.207 [http-nio-8081-exec-2] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to http://localhost:8081/oauth2/authorization/keycloak
12:11:49.208 [http-nio-8081-exec-2] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext
12:11:49.209 [http-nio-8081-exec-2] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext
12:11:49.209 [http-nio-8081-exec-2] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request
12:11:49.228 [http-nio-8081-exec-4] DEBUG o.k.adapters.PreAuthActionsHandler - adminRequest http://localhost:8081/oauth2/authorization/keycloak
12:11:49.229 [http-nio-8081-exec-4] DEBUG o.k.a.t.AbstractAuthenticatedActionsValve - AuthenticatedActionsValve.invoke /oauth2/authorization/keycloak
12:11:49.229 [http-nio-8081-exec-4] DEBUG o.k.a.AuthenticatedActionsHandler - AuthenticatedActionsValve.invoke http://localhost:8081/oauth2/authorization/keycloak
12:11:49.229 [http-nio-8081-exec-4] DEBUG o.k.a.AuthenticatedActionsHandler - Policy enforcement is disabled.
12:11:49.229 [http-nio-8081-exec-4] DEBUG o.s.security.web.FilterChainProxy - Securing GET /oauth2/authorization/keycloak
12:11:49.229 [http-nio-8081-exec-4] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
12:11:49.234 [http-nio-8081-exec-4] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to http://localhost:8090/auth/realms/main-authentication/protocol/openid-connect/auth?response_type=code&client_id=login-app&scope=openid&state=aoSFWCq-HdIiwwi_SB8oKFI29T2EJhHQmlz0e8h9RAY%3D&redirect_uri=http://localhost:8081/login/oauth2/code/keycloak&nonce=Dlsh8t93QaYB3oqVvAtlvssCEF_UBxdibFHfyIPGnv4
12:11:49.234 [http-nio-8081-exec-4] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext
12:11:49.234 [http-nio-8081-exec-4] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext
12:11:49.234 [http-nio-8081-exec-4] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request
问题解答

1. 问题原因分析

从日志可见,Spring Security的FilterSecurityInterceptor判定请求未通过授权,触发LoginUrlAuthenticationEntryPoint进行重定向。核心原因是当前配置仅启用了OAuth2授权码模式的页面登录(oauth2Login()),未配置JWT Bearer令牌的认证逻辑,导致系统无法识别请求中的Bearer令牌,直接判定为未认证,进而重定向到登录页。

另外,注释http.oauth2Login()后仍失效,是因为移除页面登录逻辑后,未补充JWT认证的配置,此时系统没有有效的认证入口,依然会触发默认的重定向行为。

2. 同时支持两种认证方式的配置方案

需要在Spring Security配置中同时注册OAuth2登录过滤器和JWT Bearer认证过滤器,并配置对应的认证管理器和入口点,让系统能根据请求类型自动选择认证方式。

核心配置代码示例

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 配置授权规则
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            // 启用OAuth2页面登录
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/oauth2/authorization/keycloak")
            )
            // 启用JWT Bearer认证
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                )
            )
            // 配置认证入口点:区分浏览器请求和API请求
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(new DelegatingAuthenticationEntryPoint(
                    List.of(
                        // API请求返回401
                        new MediaTypeRequestMatcher(MediaType.APPLICATION_JSON),
                        new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest")
                    ),
                    // 浏览器请求重定向到登录页
                    new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/keycloak")
                ))
            );

        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        // 替换为你的Keycloak JWKS端点
        String jwksUri = "http://localhost:8090/auth/realms/main-authentication/protocol/openid-connect/certs";
        return NimbusJwtDecoder.withJwkSetUri(jwksUri).build();
    }
}

关键配置说明

  • oauth2Login():保留页面登录支持,处理浏览器端的授权码流程。
  • oauth2ResourceServer().jwt():添加JWT Bearer认证支持,让系统能解析并验证请求头中的Authorization: Bearer <token>。
  • 自定义AuthenticationEntryPoint:区分浏览器请求和API请求,浏览器请求重定向到登录页,API请求返回401 Unauthorized,避免API被错误重定向。
  • JwtDecoder:通过Keycloak的JWKS端点获取公钥,用于验证JWT签名的合法性。

额外注意事项

  • 确保Keycloak客户端配置中,同时启用了Authorization Code和允许使用JWT令牌访问。
  • Postman请求时,需正确设置Authorization头为Bearer <你的JWT令牌>,并确保令牌的aud(受众)包含当前应用的client_id。
  • 若使用Spring Boot 3.x,需注意依赖版本适配,引入spring-boot-starter-oauth2-resource-server和spring-boot-starter-oauth2-client依赖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 18:43:12