基于Keycloak实现SSO单点认证,但显示”无法获取用户凭证”
本帖最后由 tzengsh_BTstt 于 2024-5-31 14:49 编辑管理员您好,
在Keycloak已設置O2OA的客戶端,手動呼叫API後確認可取得使用者資訊。
但在O2OA进行单点认证时在点击SSO图标,跳转到Keycloak登陆页面,输入用户账号密码后,跳转回O2OA页面却显示”无法获取用户凭证”,点击连结再次登陆仍回到此页面。连结如下:https://o2.-------.com/x_desktop/oauth.html?oauth=o2oa&session_state=dbf4b7e6-876d-4738-be64-02bbc2d65a3f&code=32ae6085-f513-43b8-b9d3-1bf230e7545d.dbf4b7e6-876d-4738-be64-02bbc2d65a3f.963fb10c-bb1f-4495-b080-6525d8ca6d24
确认Keycloak该名使用者已经登陆完成
O2OA的OAuth服务端配置如下图OAuth服务端配置参数详细如下请求密钥参数 response_type=code&client_id={$clientId}&scope=openid&redirect_uri={$redirect_uri}请求令牌参数 grant_type=authorization_code&client_id={$clientId}&client_secret={$clientSecret}&code={$code}&redirect_uri={$redirect_uri}请求信息参数 access_token={$access_token}
※另外要请教图2红框处`绑定用户字段`要在哪里设定?
O2OA系统log
application.request.log
172.17.1.1 - - "GET /x_desktop/oauth.html?oauth=o2oa&session_state=dbf4b7e6-876d-4738-be64-02bbc2d65a3f&code=32ae6085-f513-43b8-b9d3-1bf230e7545d.dbf4b7e6-876d-4738-be64-02bbc2d65a3f.963fb10c-bb1f-4495-b080-6525d8ca6d24 HTTP/1.1" 200 2035 1 ""
172.17.1.1 - - "GET /o2_core/o2/lp/zh-tw.js HTTP/1.1" 404 380 1 ""
172.17.1.1 - - "GET /x_desktop/res/config/config.json?v=-a5252b2&lwu9lmgl HTTP/1.1" 200 499 0 ""
172.17.1.1 - - "GET /x_program_center/jaxrs/distribute/assemble/source/o2.-------.com?v=-a5252b2 HTTP/1.1" 304 0 2 "anonymous"
172.17.1.1 - - "GET /x_organization_assemble_authentication/jaxrs/authentication/oauth/login/name/o2oa/code/32ae6085-f513-43b8-b9d3-1bf230e7545d.dbf4b7e6-876d-4738-be64-02bbc2d65a3f.963fb10c-bb1f-4495-b080-6525d8ca6d24/redirecturi/https%253A%252F%252Fo2.-------.com%252Fx_desktop%252Foauth.html%253Foauth%253Do2oa?v=-a5252b2 HTTP/1.1" 500 346 25 "anonymous"
127.0.0.1 - - "POST /x_program_center/jaxrs/unexpectederrorlog HTTP/1.1" 200 167 2 "cipher"
out.log
2024-05-31 13:49:51.561 ERROR com.x.organization.assemble.authentication.jaxrs.authentication.AuthenticationAction - id:aea8e362-7f23-4773-9d68-1c36cd8f049e, name:com.x.organization.assemble.authentication.jaxrs.authentication.AuthenticationAction, message:connection{url:https://meet.-------.com/keycloa ... penid-connect/token}, response error{responseCode:400}, response:{"error":"invalid_grant","error_description":"Incorrect redirect_uri"}., exception:java.lang.IllegalStateException, id:aea8e362-7f23-4773-9d68-1c36cd8f049e, name:com.x.organization.assemble.authentication.jaxrs.authentication.AuthenticationAction, message:connection{url:https://meet.-------.com/keycloa ... penid-connect/token}, response error{responseCode:400}, response:{"error":"invalid_grant","error_description":"Incorrect redirect_uri"}., exception:java.lang.IllegalStateException, person:anonymous, method:GET, request:http://o2.-------.com/x_organiza ... 253Do2oa?v=-a5252b2, remoteHost:172.17.1.1, emoteAddr:172.17.1.1, head:Cookie:x-token=anonymous
Accept:text/html,application/json,*/*
X-Requested-With:XMLHttpRequest
Connection:Upgrade
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Referer:https://o2.-------.com/x_desktop ... 5-b080-6525d8ca6d24
Sec-Fetch-Site:same-origin
Sec-Fetch-Dest:empty
Host:o2.-------.com
Accept-Encoding:gzip, deflate, br, zstd
Sec-Fetch-Mode:cors
sec-ch-ua:"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"
sec-ch-ua-mobile:?0
sec-ch-ua-platform:"Windows"
X-Request:JSON
Accept-Language:zh-TW
Content-Type:application/json; charset=UTF-8, body:..
java.lang.IllegalStateException: connection{url:https://meet.-------.com/keycloa ... penid-connect/token}, response error{responseCode:400}, response:{"error":"invalid_grant","error_description":"Incorrect redirect_uri"}.
at com.x.base.core.project.connection.HttpConnection.readResultString(HttpConnection.java:294) ~
at com.x.base.core.project.connection.HttpConnection.postAsString(HttpConnection.java:126) ~
at com.x.base.core.project.connection.HttpConnection.postAsString(HttpConnection.java:112) ~
at com.x.organization.assemble.authentication.jaxrs.authentication.BaseAction.oauthClientTokenPost(BaseAction.java:365) ~
at com.x.organization.assemble.authentication.jaxrs.authentication.BaseAction.oauthToken(BaseAction.java:390) ~
at com.x.organization.assemble.authentication.jaxrs.authentication.ActionOauthLogin.execute(ActionOauthLogin.java:38) ~
at com.x.organization.assemble.authentication.jaxrs.authentication.AuthenticationAction.oauthLogin(AuthenticationAction.java:374) ~
at jdk.internal.reflect.GeneratedMethodAccessor855.invoke(Unknown Source) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52) ~
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124) ~
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167) ~
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$VoidOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:159) ~
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79) ~
at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475) ~
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397) ~
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81) ~
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255) ~
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248) ~
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244) ~
at org.glassfish.jersey.internal.Errors.process(Errors.java:292) ~
at org.glassfish.jersey.internal.Errors.process(Errors.java:274) ~
at org.glassfish.jersey.internal.Errors.process(Errors.java:244) ~
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265) ~
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234) ~
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684) ~
at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394) ~
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346) ~
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:366) ~
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:319) ~
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205) ~
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:763) ~
at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1633) ~
at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:228) ~
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) ~
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1609) ~
at com.x.base.core.project.jaxrs.AnonymousCipherManagerUserJaxrsFilter.doFilter(AnonymousCipherManagerUserJaxrsFilter.java:37) ~
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) ~
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1609) ~
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:561) ~
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) ~
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:602) ~
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:1612) ~
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) ~
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1434) ~
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) ~
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) ~
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1582) ~
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) ~
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1349) ~
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~
at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:59) ~
at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:766) ~
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:383) ~
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:556) ~
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375) ~
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273) ~
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.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336) ~
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313) ~
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171) ~
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129) ~
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375) ~
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:773) ~
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:905) ~
at java.lang.Thread.run(Thread.java:829) ~[?:?]
系统版本: 9.0.3部署方式: Docker
{$redirect_uri}改为{$redirectUri},绑定用户字段不要绑定 多谢回复,发现修改后Keycloak显示"无效参数:redirect_uri",
发现网址后面的请求密钥参数会变成
response_type=code&client_id=o2oa&scope=openid&redirect_uri=&redirect_uri={$redirectUri}
的形式(多一个&redirect_uri=)
你确定你配置的参数没有问题? 本帖最后由 tzengsh_BTstt 于 2024-6-7 10:46 编辑
Hi, 启蒙星:
我先将请求密钥参数/请求令牌参数的redirect_uri都改为{$redirectUri}
但Keycloak会返回异常"Resource not found"
网址为<keycloak_uri>/?error=invalid_request&error_description=duplicated+parameter&iss=<issuer_uri>
(这应该是指之前redirect_url变成两个的错误)
但因为我看到O2OA源码确实是如您说的"redirectUri",
所以我这次只修改请求令牌参数,已经成功取得请求令牌
Keycloak的错误已变为"USER_INFO_REQUEST_ERROR"(取得用户信息失败)
用arthas对Keycloak报错行数逐步取得函数执行结果,
`watch org.keycloak.services.managers.AppAuthManager extractAuthorizationHeaderTokenOrReturnNull "{params}" -x 4`
与手动方式比对之下,发现Keycloak接收少了Authorization 请求头,如下图
手动方式发送
O2OA发送
后来在论坛看到两篇讨论
1. o2oa是否支持BearerToken
https://www.o2oa.net/forum/forum ... 83&highlight=bearer
2. oauth2单点登录 通过header中的 Authorization 进行认证如何实现
https://www.o2oa.net/forum/forum ... 42&highlight=header
再结合Gitee未合并的pull request
寻思是否为O2OA还未能发送Authorization 请求头的bearer token对Keycloak userinfo端点请求使用者信息? 依教程https://o2oa.gitbook.io/course/yuan-ma-de-bian-yi-ji-guan-li/cheng-xu-tiao-shi/hou-duan-kai-fa针对docker容器内的O2OA进行远程调试
发现确实已取得access_token
但接下来在发送使用者信息请求时,请求头是null
你的分析很细致,某些oauth认证中心会有要求额外的加密头认证,平台实现的oauth是通用的规范,比如可以配置gitee、微信、微博等oauth单点,按照您的分析需要二次开发才能实现,您可以联系我们的商务获取支持或者参照热心用户的提交修改 与Keycloak整合的问题已解决。
其实我们后续会订阅技术支持,但目前高层的态度是尽量不二次开发,
因为之前导入产品有遇过二次开发导致无法预料的不断返工和品质问题。
也遇过产品升级造成之前客制化逻辑消失和错误重现,高层会担心无法平滑升级。
所以会希望Keycloak整合是稳定的主线功能。
选项之一是自行修改,
在后续测试时得知Keycloak也支持请求体提交access_token的方式
(OAuth 2.0 RFC6750)
但取得使用者信息会因为缺少请求头Content-Type而失败。
查找源码发现在取得access_token时其实有传送请求头,
但不知为何在取得使用者信息却选择不传请求头,
推测可能为了通用性,要符合gitee、微信、微博等接口而为,
虽然只需要把这2行传送请求头代码新增到取得使用者信息接口逻辑即可(如下囥),
但因为基于不二次开发的原则,把这段逻辑合并回主线也可能失败而作罢。
选项之二是开发Keycloak扩展程序,拦截O2OA的请求并新增请求头,
但也考量到未来Keycloak升级后有可能造成扩展程序失效而作罢。
最后选择选项之三,取消Keycloak本身的HTTPS功能,
在其前面加上反向proxy(我们是用Nginx),直接在Nginx做请求头处理。
反向proxy是常规操作,其他系统也会用得到。
而Nginx location设定可如下设定之
location /keycloak/realms/my_realm/protocol/openid-connect/o2oa_userinfo { # <-为O2OA新设的接口,O2OA请求信息地址改设这个位址
......
proxy_set_header Content-Type "application/x-www-form-urlencoded"; # <- 新增这个请求头
proxy_pass http://keycloak_ip:keycloak_port/keycloak/realms/my_realm/protocol/openid-connect/userinfo; # <-实际向Keycloak请求信息位址
}
页:
[1]