Cometd, Spring-security :当前经过身份验证的用户在侦听器中不可用

Cometd, Spring-security : Currently authenticated user not available inside a Listener

提问人:We are Borg 提问时间:10/12/2018 更新时间:2/1/2019 访问量:227

问:

我正在处理一个 Spring-MVC 项目,其中我使用 Spring-security 进行身份验证和授权。除此之外,我还使用 Cometd 库通过 websockets 发送消息。在侦听器中收到消息后,当我尝试获取当前经过身份验证的用户时,它始终为 null。

如何确保 Cometd 中的每个请求至少包含 Spring-security 识别所需的 JSESSIONID?

或者 Spring-security 中是否有一些设置可以使这成为可能。

当我抬头时,有很多用户面临这个问题,但没有明确的答案或有用的代码。

测试代码:

  @Listener(value = "/service/testlistener/{id}")
    public void testlistener(ServerSession remote,
                             ServerMessage message, @Param("id") String id) {
        try {            
          Person user = this.personService.getCurrentlyAuthenticatedUser();
            Map<String, Object> data = message.getDataAsMap();
           System.out.println("currentlyAuthenticatedUser: " + user + " \nTransmitted Data in map:" + data.get("name"));
            Map<String, Object> output = new HashMap<>();
            output.put("name", user.getFirstName());
            ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/" + id).getReference();
            serverChannel.setPersistent(false);
            serverChannel.publish(serverSession, output);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

彗星.js :

 var connectionIntervall = null;
    var onlineElement = document.getElementById("browser-online");
    var cometd = $.cometd;
    cometd.configure({
        url: navigationController.generateCometDUrl(),
        logLevel: 'error',
        stickyReconnect: false,
        appendMessageTypeToURL: false,
        requestHeaders : navigationController.generateCometDHeaders()
    });

    var connects = 0;

    cometd.addListener('/meta/handshake', _metaHandshake);
    cometd.addListener('/meta/connect', _metaConnect);
    cometd.websocketEnabled = true;

    cometd.handshake();
Java Spring-Security 彗星

评论


答:

1赞 Łukasz Kotyński 1/30/2019 #1

解决方案: - 彗星D 4.0.2 - 春季 4.3.9.RELEASE - Spring Security 4.1.0.发布

在我的项目中,我们在从“长池”切换到“websocket”连接时遇到了类似的问题。为了澄清 - 一个区别 - 我们仅在“握手”期间使用“用户上下文”。

通过使用“长池化”,Spring 正确解释了带有 JSESSIONID 的 Cookie,我们在“用户上下文”中工作。切换到“websocket”后,看起来“用户上下文”在运行消息接收代码之前就丢失了。

我们找不到合适的解决方案 - 在我看来,以下代码只是一个黑客。但它对我们有用。

import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.DefaultSecurityPolicy;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

public class BayeuxAuthenticator extends DefaultSecurityPolicy implements ServerSession.RemoveListener {

    // (...)

    @Override
    public boolean canHandshake( BayeuxServer server, ServerSession session, ServerMessage message ) {
        SecurityContextHolder.getContext().setAuthentication( ( UsernamePasswordAuthenticationToken ) message.getBayeuxContext().getUserPrincipal() );
        String user = securityService.getUserName();
        // (...)

        return true;
    }

    // (...)
}

其中以下行是最重要的

SecurityContextHolder.getContext().setAuthentication( ( UsernamePasswordAuthenticationToken ) message.getBayeuxContext().getUserPrincipal() );

编辑 2:

我们从以下位置接收数据

String user = securityService.getUserName();

其中 securityService 是 Spring @Service 受 Spring @Secured(/* 预期角色 */) 保护的。(@Secured注释在设置上下文之前为我们生成了授权异常)

里面有上下文读取和用户数据:

SecurityContextHolder.getContext().getUserPrincipal().getName()

但老实说,如果你只需要“用户主体”,那么从以下位置读取它可能就足够了:

message.getBayeuxContext().getUserPrincipal() // instanceof java.security.Principal

在我们的例子中,这是类型

org.springframework.security.authentication.UsernamePasswordAuthenticationToken

也许在你的情况下,它是不同的子类?

澄清一下 - 在 Web 套接字连接启动期间,我们在请求中传递了具有正确 JSESSIONID 的 Cookie。

WebSocket 连接请求:

Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,pl;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Cookie: XSRF-TOKEN=<some token>; Idea-<some token>; lastOpenTab=sourcing/content; io=<some token>; BAYEUX_BROWSER=<some token>; JSESSIONID=<some token>
Host: localhost:8090
Origin: http://localhost:8090
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: NTtWtXAL8ReIB0LjvI2G0g==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 ...

评论

0赞 We are Borg 1/30/2019
谢谢。我本来希望这能起作用,但消息对象不包含任何方法 message.getBayeuxContext()。我们正在使用 cometd 3.1.2
0赞 Łukasz Kotyński 1/30/2019
我的错。我向解决方案添加了版本。
0赞 We are Borg 1/30/2019
哦,那么我们可以升级到您的版本并试用一下。明天早上会检查它,因为办公室现在已经结束了,然后恢复。谢谢伙计。:-D
0赞 We are Borg 1/31/2019
我升级了库,但我仍然得到用户为空的 NPE。您能告诉我您如何检索您身边当前经过身份验证的用户吗?谢谢。
0赞 Łukasz Kotyński 2/1/2019
我将 resposne 添加为“Edit 2”。此外,我在帖子的开头添加了有关 Spring 版本的信息。也许您应该检查从 BayeuxContext 返回的 Principals 是否不为 null?如果是这样,通信中是否传递了 JSESSIONID Cookie?您可以在文章末尾添加 WebSocket 连接请求。请记住,我是在 BayeuxAuthenticator 中执行此操作的 - 也许它在其余通信中不可用?:(如果您找到解决方案,请在此处发布。也许有一天我会需要它;)