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

整合Keycloak、Shiro、Pac4j到Zeppelin时PKCE验证失败:Code mismatch

PKCE验证失败:Code mismatch(Keycloak+Shiro+Pac4j整合Zeppelin)

问题概述

将Keycloak、Apache Shiro与Pac4j整合到Apache Zeppelin(版本0.11.1)做身份认证时,触发PKCE验证失败,报错PKCE verification failed: Code mismatch,对应HTTP 500错误。谷歌搜索未找到同类问题解决方案,寻求技术支持。

报错堆栈

HTTP ERROR 500 javax.servlet.ServletException: org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
URI:    /api/callback
STATUS: 500
MESSAGE:    javax.servlet.ServletException: org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
SERVLET:    rest
CAUSED BY:  javax.servlet.ServletException: org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
CAUSED BY:  org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
Caused by:
javax.servlet.ServletException: org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
    at org.apache.shiro.web.servlet.AdviceFilter.cleanup(AdviceFilter.java:196)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:148)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458)
    at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373)
    at org.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    at org.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    at org.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1626)
    at org.apache.zeppelin.server.CorsFilter.doFilter(CorsFilter.java:64)
    at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193)
    at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1626)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:552)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:505)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:234)
    at io.micrometer.core.instrument.binder.jetty.TimedHandler.handle(TimedHandler.java:120)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at org.eclipse.jetty.server.Server.handle(Server.java:516)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
    at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
    at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=PKCE verification failed: Code mismatch
    at org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.executeTokenRequest(OidcAuthenticator.java:206)
    at org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.validate(OidcAuthenticator.java:165)
    at org.pac4j.core.client.BaseClient.lambda$retrieveCredentials$0(BaseClient.java:75)
    at java.base/java.util.Optional.ifPresent(Optional.java:183)
    at org.pac4j.core.client.BaseClient.retrieveCredentials(BaseClient.java:72)
    at org.pac4j.core.client.IndirectClient.getCredentials(IndirectClient.java:145)
    at org.pac4j.core.engine.DefaultCallbackLogic.perform(DefaultCallbackLogic.java:75)
    at org.pac4j.jee.filter.CallbackFilter.internalFilter(CallbackFilter.java:71)
    at org.pac4j.jee.config.AbstractConfigFilter.doFilter(AbstractConfigFilter.java:72)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    ... 42 more
Powered by Jetty:// 9.4.52.v20230823

shiro.ini配置

[main]
roleAdminAuthGenerator = org.pac4j.core.authorization.generator.FromAttributesAuthorizationGenerator
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 86400000

### Define CodeChallengeMethod
#S256 = com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod
#S256.name = S256

### OIDC Pac4j Config
oidcConfig = org.pac4j.oidc.config.OidcConfiguration
oidcConfig.withState = false
oidcConfig.discoveryURI = http://localhost:8323/realms/zeppelin/.well-known/openid-configuration
oidcConfig.clientAuthenticationMethodAsString = client_secret_basic
oidcConfig.clientId = zeppelin-client
oidcConfig.secret = <secret>
oidcConfig.scope = openid
oidcConfig.useNonce = true
oidcConfig.responseType = code
oidcConfig.logoutUrl = http://localhost:8323/realms/zeppelin/protocol/openid-connect/logout

oidcClient = org.pac4j.oidc.client.OidcClient
oidcClient.name = oidcClient
oidcClient.configuration = $oidcConfig
oidcClient.authorizationGenerator = $roleAdminAuthGenerator

### Pac4J Client Details
clients = org.pac4j.core.client.Clients

requireRoleAdmin = org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer
requireRoleAdmin.elements = admin_role
requireRoleUser = org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer
requireRoleUser.elements = admin_role, user_role

### Pac4j Config
config = org.pac4j.core.config.Config
config.clients = $clients
config.authorizers = admin:$requireRoleAdmin, user:$requireRoleUser

### Pac4jRealm and SecurityFilter
pac4jRealm = io.buji.pac4j.realm.Pac4jRealm
pac4jRealm.principalNameAttribute = preferred_username
pac4jSubjectFactory = io.buji.pac4j.subject.Pac4jSubjectFactory

securityManager.realms = $pac4jRealm
securityManager.subjectFactory = $pac4jSubjectFactory
oidcSecurityFilter = org.pac4j.jee.filter.SecurityFilter
oidcSecurityFilter.clients = oidcClient
oidcSecurityFilter.config = $config

### Logout Filter
logoutFilter = org.pac4j.jee.filter.LogoutFilter
logoutFilter.localLogout = true
logoutFilter.centralLogout = true
logoutFilter.config = $config
logoutFilter.defaultUrl = http://localhost:8323/realms/zeppelin/protocol/openid-connect/logout

### Ajax Resolvers
ajaxRequestResolver = org.pac4j.core.http.ajax.DefaultAjaxRequestResolver
ajaxRequestResolver.addRedirectionUrlAsHeader = true
oidcClient.ajaxRequestResolver = $ajaxRequestResolver

### Callback Filters
callbackFilter = org.pac4j.jee.filter.CallbackFilter
callbackFilter.defaultUrl = http://localhost:8080
callbackFilter.config = $config

clients.callbackUrl = http://localhost:8080/api/callback
clients.clients = $oidcClient

[urls]
/api/version = anon
/api/callback = callbackFilter
/api/login/logout = logoutFilter
/** = oidcSecurityFilter

依赖库信息

asm-1.0.2.jar
buji-pac4j-8.1.0.jar
content-type-2.3.jar
javaee-pac4j-7.1.0.jar
json-smart-2.5.1.jar
lang-tag-1.7.jar
pac4j-config-5.7.6.jar
pac4j-core-5.7.6.jar
pac4j-http-5.7.6.jar
pac4j-javaee-5.7.6.jar
pac4j-oauth-5.7.6.jar
pac4j-oidc-5.7.6.jar
oauth2-oidc-sdk-11.13.jar

排查方向

  • 检查session存储:Pac4j默认用session存储code_verifier,确认Zeppelin的session配置是否正常,无丢失或序列化异常
  • 配置Code Challenge Method:当前shiro.ini注释了S256配置,确认Keycloak客户端是否强制要求S256,同时在Pac4j中启用对应方法
  • 核对回调URL:确保Keycloak客户端配置的回调URL与clients.callbackUrl完全一致(包括协议、端口、路径)
  • 验证依赖兼容性:确认buji-pac4j 8.1.0与pac4j 5.7.6版本匹配,可查阅官方版本说明
  • 临时禁用PKCE测试:在oidcConfig中添加oidcConfig.usePkce = false,确认是否为PKCE机制本身问题,再逐步排查

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:30:58