通过 openresty 使用 keycloak 对 websocket 进行身份验证
Posted
技术标签:
【中文标题】通过 openresty 使用 keycloak 对 websocket 进行身份验证【英文标题】:Authenticate websocket with keycloak through openresty 【发布时间】:2019-07-28 09:28:31 【问题描述】:目前我有一个包含以下组件的有效解决方案:
带有自定义应用程序的网络服务器 使用 lua 的 Openresty 钥匙斗篷这允许我使用 keycloak 进行身份验证。 因为我的网络服务器还公开了一个 websocket 主机,所以我也想对这些 websocket 进行身份验证。有没有人有一个示例(nginx 文件作为 lua 文件)可用于使用 openresty 验证 websocket 连接?我已经查看了https://github.com/openresty/lua-resty-websocket,但似乎无法在身份验证部分找到插件的位置。 一个测试这个的示例客户端应用程序也很棒!
【问题讨论】:
【参考方案1】:我自己想通了。在此处发布我的解决方案以帮助其他人实现相同的目标。 我有以下代码 sn-ps:
Openresty 配置
仅适用于 websocket,应放在服务器部分:
set $resty_user 'not_authenticated_resty_user';
location /ws
access_by_lua_file /usr/local/openresty/nginx/conf/lua_access.lua;
proxy_pass http://<backend-websocket-host>/ws;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwared-User $resty_user;
proxy_read_timeout 1d;
proxy_send_timeout 1d;
lua_acces.lua
local opts =
redirect_uri = "/*",
discovery = "http://<keycloak-url>/auth/realms/realm/.well-known/openid-configuration",
client_id = "<client-id>",
client_secret = "<client-secret>",
redirect_uri_scheme = "https",
logout_path = "/logout",
redirect_after_logout_uri = "http://<keycloak-url>/auth/realms/realm/protocol/openid-connect/logout?redirect_uri=http%3A%2F%2google.com",
redirect_after_logout_with_id_token_hint = false,
session_contents = id_token=true,
ssl_verify=no
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").bearer_jwt_verify(opts)
if err or not res then
print("Token authentication not succeeded")
if err then
print("jwt_verify error message:")
print(err)
end
if res then
print("jwt_verify response:")
tprint(res)
end
res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
if res.id_token and res.id_token.preferred_username then
ngx.var.resty_user = res.id_token.preferred_username
else
ngx.var.resty_user = res.preferred_username
end
这仅在 websocket 连接从 keycloak 服务中检索到有效令牌时才允许。 最后填写resty用户,将认证用户传递给后端应用。
示例 Java 客户端应用程序
获取密钥斗篷令牌
package test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.AccessTokenResponse;
public class KeycloakConnection
private Keycloak _keycloak;
public KeycloakConnection(final String host, String username, String password, String clientSecret, String realm, String clientId)
_keycloak = Keycloak.getInstance(
"http://" + host + "/auth",
realm,
username,
password,
clientId,
clientSecret);
public String GetAccessToken()
final AccessTokenResponse accessToken = _keycloak.tokenManager().getAccessToken();
return accessToken.getToken();
网络套接字
这个 sn-p 只包含我调用来设置 websocket 连接的函数。您仍然需要实例化 _keycloakConnection 对象,在我的情况下,我有一个通用的 _session 字段,可以在每次需要时重复使用会话。
private Session GetWebsocketSession(String host)
URI uri = URI.create("wss://" + host);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader("Authorization", "Bearer " + _keycloakConnection.GetAccessToken());
_client = new WebSocketClient();
try
_client.start();
// The socket that receives events
WebsocketEventHandler socketEventHandler = new WebsocketEventHandler(this::NewLiveMessageReceivedInternal);
// Attempt Connect
Future<Session> fut = _client.connect(socketEventHandler, uri, request);
// Wait for Connect
_session = fut.get();
return _session;
catch (Throwable t)
_logger.error("Error during websocket session creation", t);
return null;
WebsocketEventHandler
在这个类中注入一个消费者来消费另一个类中的消息
package test;
import org.apache.log4j.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import java.util.function.Consumer;
public class WebsocketEventHandler extends WebSocketAdapter
private final Logger _logger;
private Consumer<String> _onMessage;
public WebsocketEventHandler(Consumer<String> onMessage)
_onMessage = onMessage;
_logger = Logger.getLogger(WebsocketEventHandler.class);
@Override
public void onWebSocketConnect(Session sess)
super.onWebSocketConnect(sess);
_logger.info("Socket Connected: " + sess);
@Override
public void onWebSocketText(String message)
super.onWebSocketText(message);
_logger.info("Received TEXT message: " + message);
_onMessage.accept(message);
@Override
public void onWebSocketClose(int statusCode, String reason)
super.onWebSocketClose(statusCode,reason);
_logger.info("Socket Closed: [" + statusCode + "] " + reason);
@Override
public void onWebSocketError(Throwable cause)
super.onWebSocketError(cause);
_logger.error("Websocket error", cause);
发送消息
创建 _session 后,您可以使用以下行发送数据:
_session.getRemote().sendString("Hello world");
这些 sn-ps 都是我整个解决方案的一小部分。我可能错过了什么。如果有人有问题或这不适用于您的情况,请联系我们,我会提供更多信息。
【讨论】:
以上是关于通过 openresty 使用 keycloak 对 websocket 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章
使用 cUrl 通过其 REST API 配置 Keycloak
使用 JAVA 通过 REST API 集成 Keycloak 时在 toRepresentation() 处获取 404