如何诊断 Quarkus REST 服务 OIDC 错误(特别是 403 禁止)?

How does one diagnose Quarkus REST service OIDC errors (specifically 403 forbidden)?

我正在尝试使用 OIDC 承载来保护 Quarkus (v 1.13.7) REST 服务。我在关注他们 Using OpenID Connect to Protect Service Applications guide,但运气不佳。

每当我在我对受保护资源的请求的 header 中包含 Authorization: Bearer {validAccessToken} 时(使用 @RolesAllowed 注释),我都会收到一个 403 Forbidden 响应,其中包含一个空 body。如果我省略这个 header,我可以访问未受保护的资源,但受保护的资源,不出所料,给 401 Unauthorized,同样是一个空的 body.

这是我使用的应用程序属性:

# DB configuration
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = bla
quarkus.datasource.password = bla_bla
quarkus.hibernate-orm.database.generation = update
quarkus.datasource.jdbc.url = jdbc:postgresql://blablabla

# Logging
%dev.quarkus.log.level=ALL
%dev.quarkus.log.category."io.quarkus.oidc".level=FINEST
%dev.quarkus.log.category."io.quarkus.security".level=FINEST

# OIDC config
demo.oidc-provider=valid.provider.url.com/bla
demo.oidc-clientid=validClientId
demo.oidc-clientsecret=validClientSecret
demo.oidc-issuer=${demo.oidc-provider}

quarkus.oidc.application-type=service
quarkus.oidc.auth-server-url=${demo.oidc-provider}
quarkus.oidc.client-id=${demo.oidc-clientid}
quarkus.oidc.credentials.client-secret.value=${demo.oidc-clientsecret}
quarkus.oidc.token.issuer=${demo.oidc-issuer}

quarkus.oidc.authentication.user-info-required=true
quarkus.oidc.roles.source=userinfo
quarkus.oidc.roles.role-claim-path=userroles
quarkus.oidc.discovery-enabled=false
quarkus.oidc.introspection-path=/introspect
quarkus.oidc.user-info-path=/userinfo

注意日志部分。尽管它被配置为记录所有内容,但当我收到 403 响应时,它会打印所有内容:

2021-08-04 11:22:10,147 FINEST [io.ver.ext.web.imp.RouterImpl] (vert.x-eventloop-thread-6) Router: 1653352852 accepting request GET http://localhost:8080/my/api/resource/path
2021-08-04 11:22:10,171 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='null', order=-2147483648, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.vertx.http.runtime.VertxHttpRecorder@76ec9770], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=false, exclusive=false, exactPath=false}
2021-08-04 11:22:10,171 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,200 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='null', order=-200, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder@79d5df9a], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=false, exclusive=false, exactPath=false}
2021-08-04 11:22:10,202 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,203 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='null', order=-100, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder@1e4e05ec], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=false, exclusive=false, exactPath=false}
2021-08-04 11:22:10,206 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,207 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='null', order=-2, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.vertx.http.runtime.VertxHttpRecorder@6bf0fbda], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=false, exclusive=false, exactPath=false}
2021-08-04 11:22:10,208 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,209 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='null', order=10000, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.vertx.http.runtime.StaticResourcesRecorder$$Lambda5/0x0000000800786840@6ed633ec, io.quarkus.vertx.http.runtime.StaticResourcesRecorder$$Lambda5/0x0000000800786840@57426aed, io.quarkus.vertx.http.runtime.StaticResourcesRecorder$$Lambda9/0x0000000800785840@7d171a43], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=false, exclusive=false, exactPath=false}
2021-08-04 11:22:10,210 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,210 FINEST [io.ver.ext.web.han.imp.StaticHandlerImpl] (vert.x-eventloop-thread-6) File to serve is /my/api/resource/path
2021-08-04 11:22:10,215 FINEST [io.ver.ext.web.han.imp.StaticHandlerImpl] (vert.x-eventloop-thread-6) File to serve is /my/api/resource/path
2021-08-04 11:22:10,218 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Route matches: RouteState{path='/', order=10001, enabled=true, methods=null, consumes=null, emptyBodyPermittedWithConsumes=false, produces=null, contextHandlers=[io.quarkus.resteasy.runtime.standalone.VertxRequestHandler@43d422b0], failureHandlers=null, added=true, pattern=null, groups=null, useNormalisedPath=true, namedGroupsInRegex=null, virtualHostPattern=null, pathEndsWithSlash=true, exclusive=false, exactPath=false}
2021-08-04 11:22:10,219 FINEST [io.ver.ext.web.imp.RoutingContextImplBase] (vert.x-eventloop-thread-6) Calling the  handler
2021-08-04 11:22:10,222 DEBUG [org.jbo.res.res.i18n] (executor-thread-1) RESTEASY002315: PathInfo: /my/api/resource/path

也许对某个地方的某个人来说,以上信息很有用。对我来说,它们没有用。有趣的是,如果令牌已过期,我实际上会收到一条消息表明这一点。

上帝的绿色地球(或您喜欢的任何神灵和行星属性)我应该如何诊断这个问题?

我已经尝试使用 Quarkus exception mapping to try and catch the problem and inspect the exception, but no matter how I configure the exception mapping, it never gets called. According to issue #8570 on the quarkus git repo:

[...] to map authentication failures as well as authorisation then you need to disable proactive auth, and then you can use an exception mapper for the resulting AuthenticationFailedException, as it will be generated from the interceptor rather than early in security handling.

所以我将 quarkus.http.auth.proactive=false 添加到我的应用程序属性,现在 Quarkus returns a 500 Internal Error 说它不能调用 getIdentity() 我不知道什么线程,我应该注入这个身份或其他类似的东西。问题是,我并没有试图在任何地方获得这个身份(至少现在还没有),所以这个失败是在他们自己的内部代码中。因此我放弃了尝试使用异常映射。

编辑:我还应该提到 OIDC 信息(客户端 ID、URL 等)是有效的。我在我的 Angular 应用程序中使用它们,它们工作得很好。

此刻,验证令牌的 OidcProvider class 将一些验证委托给底层的 jose-jwt 验证器。

通过启用以下日志记录类别,可以查看一些关于令牌无效原因的信息:

quarkus.log.category."io.quarkus.oidc".level=TRACE

# The category io.quarkus.oidc already contains the errors generated by the jose-jwt library

将出现的错误通常与配置错误、过期令牌或无效签名有关。您将不会看到有关允许角色访问资源的错误。

所以会出现一些 403 错误的原因,而其他则不会。

通常我在开发新应用程序时遇到 403 错误时检查的内容是确保受众、声明和角色(组)正确存在于提供商颁发的令牌中。大多数情况下,错误是未正确设置的三个错误之一。

我将打开增强功能以​​统一或至少包含尽可能多的错误或改进有关此主题的文档。

也许他们可以做点什么或者给我们指明更好的方向。