整合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
相关产品推荐
相关产品推荐

