首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在WebSocket控制器中获取登录用户的主体

在WebSocket控制器中获取登录用户的主体
EN

Stack Overflow用户
提问于 2021-01-27 12:32:23
回答 1查看 704关注 0票数 2

我有一个Spring应用程序,它使用Security来使用JWT来保护端点,这对于标准的REST控制器都是正确的。该应用程序还具有WebSocket连接,我已经成功地使用相同的JWT保护了这些连接。

问题

如果我试图将主体注入到WebSocket控制器方法中,则在默认情况下主体为空。我已经找到了许多通过重写DefaultHandshakeHandler来设置主体的解决方案,但是这些解决方案中的大多数都会生成一个随机的主体名称。我宁愿使用现有的登录用户名来生成主体名称,这样我就可以看到哪个用户正在向WebSocket发送数据并相应地处理响应。

这里有一个答复,我认为它可以通过添加使我更接近,但是当将逻辑应用到我的preSend方法时,即使我将它作为StompJS连接方法的一个参数添加,也会得到一个空用户名:WebSocket Stomp over SockJS - http custom headers

我还发现奇怪的是,在拦截器本身中,我能够使用正确的用户名访问主体,但一旦到达控制器,它要么为null,要么是preSend握手方法中定义的任何内容。

下面是连接到WebSocket的方法:

代码语言:javascript
运行
复制
  _connect() {
    console.log("Initialize WebSocket Connection");
    if (this.authenticationService.isUserLoggedIn()) {
      let ws = new SockJS(this.webSocketEndPoint);
      this.stompClient = Stomp.over(ws);
      const _this = this;
      _this.stompClient.connect({
        'Authorization': "Bearer " + this.authenticationService.getLoggedInUserToken(),
        'username': this.authenticationService.getLoggedInUserName()
      }, function (frame) {
          _this.stompClient.subscribe(_this.board, function (data) {
              _this.onDataReceived(data);
          });
      }, this.errorCallBack);
    } else {
      console.log("Unable to connect: must be logged in.");
    }
  }

这是我当前的握手代码,有注释结果:

代码语言:javascript
运行
复制
    public class MyHandshakeHandler extends DefaultHandshakeHandler {
        @Override
        protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, 
                                          Map<String, Object> attributes) {
            Principal principal = SecurityContextHolder.getContext().getAuthentication();
            System.out.println("PRINCIPAL HANDSHAKE START:" + principal); //prints annonymous user
            final String ATTR_PRINCIPAL = "__principal__";
            
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
            String username = httpServletRequest.getParameter("username");
            
            //System.out.println("Username is: " + username); // null
            final String name;
            if (!attributes.containsKey(ATTR_PRINCIPAL)) {
                name = "My made up name"; //Would like this to be the logged in user, but is currently null
                attributes.put(ATTR_PRINCIPAL, name);
            } else {
                name = (String) attributes.get(ATTR_PRINCIPAL);
            }
            return new Principal() {
                @Override
                public String getName() {
                    return name;
                }
            };
        }
    }

最后,我的拦截器授权WebSocket请求并显示正确的登录用户:

代码语言:javascript
运行
复制
@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99) 
public class WebSocketAuthenticationConfig implements WebSocketMessageBrokerConfigurer {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private UserService userService;

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                Principal principal = SecurityContextHolder.getContext().getAuthentication();
                System.out.println("PRINCIPAL PRE-SEND START:" + principal); // shows annonymous user
                
                StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
                List<String> tokenList = accessor.getNativeHeader("Authorization");
                String jwt = null;
                if (tokenList == null || tokenList.size() < 1) {
                  return message;
                } else {
                    jwt = tokenList.get(0).substring(7);
                  if (jwt == null) {
                    return message;
                  }
                }
                String username = jwtUtil.extractUsername(jwt); //Decoder class that can retrieve username from token
                
                UserDetails userDetails = userService.loadUserByUsername(username);
                if (jwtUtil.validateToken(jwt, userDetails)) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = 
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    
                    //Used only to see which principal is found at this stage
                    principal = SecurityContextHolder.getContext().getAuthentication();
                    System.out.println("PRINCIPAL PRE-SEND END:" + principal); // shows correct logged in user
                    
                    accessor.setUser(authentication);
                }
                return message;
            }
        });
    }
}

以下是当前正在对主体进行测试的Controller方法:

代码语言:javascript
运行
复制
    @MessageMapping("/test")
    public void test(@Payload String message, Principal principal) throws Exception {
        System.out.println("PRINCIPAL TEST INJECTED: " + principal.getName());
        Principal retrievedPrincipal = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("PRINCIPAL TEST RETRIEVED: " + retrievedPrincipal); //null
        webSocket.convertAndSend("/topic/test", new ResponseMessage("TestSuccess", "Thanks for the request " + principal.getName())); // shows "made up username" from handshake
    }

下面是一个名为“示例”的登录用户连接到WebSocket并尝试访问/app/test端点时的控制台输出:

代码语言:javascript
运行
复制
PRINCIPAL HANDSHAKE START:AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
PRINCIPAL PRE-SEND START:null
PRINCIPAL PRE-SEND END:UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=example, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]
PRINCIPAL PRE-SEND START:null
PRINCIPAL PRE-SEND START:null
PRINCIPAL TEST INJECTED: My made up name
PRINCIPAL TEST RETRIEVED: SecurityContextImpl [Null authentication]

请让我知道,如果您需要任何进一步的代码,以明确。

EN

回答 1

Stack Overflow用户

发布于 2022-01-08 18:57:40

以下是我经过大量研究后所做的工作。我到达这个位置(https://coderedirect.com/questions/310917/secure-spring-webscoket-using-spring-security-and-access-principal-from-websocke)是从搜索google @MessageMapping Principal is null作为查询,以查看与我们的实现有什么不同,而不是

代码语言:javascript
运行
复制
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

当我尝试

代码语言:javascript
运行
复制
StompHeaderAccessor accessor = MessageHeaderAccessor
                               .getAccessor(message, StompHeaderAccessor.class);

控制器内的Principal现在不是空的。我不确定spring-messagingMessageHeaderAccessor的深度

从我搜索的初始线程中得到的更多的参考是JSON Web Token (JWT) with Spring based SockJS / STOMP Web Socket,它最终找到了主要答案的真相来源。

https://github.com/spring-projects/spring-framework/blob/main/src/docs/asciidoc/web/websocket.adoc#token-authentication

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65919245

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档