最近遇到了一个需求,根据不同用户的权限能接收不同的消息,在websocket中获取用户信息的时候遇到了困难,发现了一篇写的很清楚的文章,记录一下。后面还有遇到的问题

原地址: https://www.cnblogs.com/zhuxiaojie/p/6238826.html

1.websocket的java代码

@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
     * 连接成功*/
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
     * 连接关闭调用的方法
    @OnClose
    public void onClose() {
     * 收到消息
     * @param message 
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自浏览器的消息:" + message);
        //群发消息
        for (MyWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
     * 发生错误时调用
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);//同步
        //this.session.getAsyncRemote().sendText(message);//异步
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
        websocket = new WebSocket("ws://localhost:9999/websocket");
    else{
        alert('不支持websocket')
    //连接发生错误
    websocket.onerror = function(){
    //连接成功
    websocket.onopen = function(event){
    //接收到消息
    websocket.onmessage = function(event){
        var msg = event.data;
        alert("收到消息:" + msg);
    //连接关闭
    websocket.onclose = function(){
    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
        websocket.close();
    //发送消息
    function send(message){
        websocket.send(message);
</script>
</html>

3.获取HttpSession,源码分析

获取HttpSession是一个很有必要讨论的问题,因为java后台需要知道当前是哪个用户,用以处理该用户的业务逻辑,或者是对该用户进行授权之类的,但是由于websocket的协议与Http协议是不同的,所以造成了无法直接拿到session。但是问题总是要解决的,不然这个websocket协议所用的场景也就没了。

3.1.获取HttpSession的工具类,源码详细分析

package javax.websocket.server;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServerEndpoint {
     * URI or URI-template that the annotated class should be mapped to.
     * @return The URI or URI-template that the annotated class should be mapped
     *         to.
    String value();
    String[] subprotocols() default {};
    Class<? extends Decoder>[] decoders() default {};
    Class<? extends Encoder>[] encoders() default {};
    public Class<? extends ServerEndpointConfig.Configurator> configurator()
            default ServerEndpointConfig.Configurator.class;

我们看到最后的一个方法,可以看到,它要求返回一个ServerEndpointConfig.Configurator的子类,我们写一个类去继承它。

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
public class HttpSessionConfigurator extends Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
    //怎么搞?

当我们覆盖modifyHandshake方法时,可以看到三个参数,其中后面两个参数让我们感觉有点见过的感觉,我们查看一HandshakeRequest的源码

package javax.websocket.server;
import java.net.URI;
import java.security.Principal;
import java.util.List;
import java.util.Map;
 * Represents the HTTP request that asked to be upgraded to WebSocket.
public interface HandshakeRequest {
    static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
    static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
    static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
    static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";
    Map<String,List<String>> getHeaders();
    Principal getUserPrincipal();
    URI getRequestURI();
    boolean isUserInRole(String role);
     * Get the HTTP Session object associated with this request. Object is used
     * to avoid a direct dependency on the Servlet API.
     * @return The javax.servlet.http.HttpSession object associated with this
     *         request, if any.
    Object getHttpSession();
    Map<String, List<String>> getParameterMap();
    String getQueryString();

我们发现它是一个接口,接口中规范了这样的一个方法

* Get the HTTP Session object associated with this request. Object is used * to avoid a direct dependency on the Servlet API. * @return The javax.servlet.http.HttpSession object associated with this * request, if any. Object getHttpSession();

上面有相应的注释,说明可以从Servlet API中获取到相应的HttpSession。

当我们发现这个方法的时候,其实已经松了一口气了。

那么我们就可以补全未完成的代码

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
 * 从websocket中获取用户session
public class HttpSessionConfigurator extends Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
          HttpSession httpSession = (HttpSession) request.getHttpSession();
          sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

其实通过上面的源码分析,你们应该知道了HttpSession的获取。但是下面又多了一行代码

sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

这行代码又是什么意思呢?

我们看一下ServerEnpointConfig的声明

public interface ServerEndpointConfig extends EndpointConfig

我们发现这个接口继承了EndpointConfig的接口,好,我们看一下EndpointConfig的源码:

package javax.websocket;
import java.util.List;
import java.util.Map;
public interface EndpointConfig {
    List<Class<? extends Encoder>> getEncoders();
    List<Class<? extends Decoder>> getDecoders();
    Map<String,Object> getUserProperties();

我们发现了这样的一个方法定义

Map<String,Object> getUserProperties();

可以看到,它是一个map,从方法名也可以理解到,它是用户的一些属性的存储,那既然它提供了get方法,那么也就意味着我们可以拿到这个map,并且对这里面的值进行操作,

所以就有了上面的

sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

那么到此,获取HttpSession的源码分析,就完成了。

3.2.设置HttpSession的类

我们之前有说过,由于HTTP协议与websocket协议的不同,导致没法直接从websocket中获取协议,然后在3.1中我们已经写了获取HttpSession的代码,但是如果真的放出去执行,那么会报空指值异常,因为这个HttpSession并没有设置进去

好,这一步,我们来设置HttpSession。这时候我们需要写一个监听器。

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class RequestListener implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre)  {
        //将所有request请求都携带上httpSession
        ((HttpServletRequest) sre.getServletRequest()).getSession();
    public RequestListener() {
    public void requestDestroyed(ServletRequestEvent arg0)  {

然后我们需要把这个类注册为监听器,请自行百度。

3.3.在websocket中获取用户的session

然后刚才我们通过源码分析,是知道@ServerEndpoint注解中是有一个参数可以配置我们刚才继承的类。好的,我们现在进行配置。

@ServerEndpoint(value = "/websocket" , configurator = HttpSessionConfigurator.class)

Config对象,并且通过这个对象,拿到之前我们设置进去的map

@OnOpen
    public void onOpen(Session session,EndpointConfig config){
        HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        User user = (User)httpSession.getAttribute(SessionName.USER);
        if(user != null){
            this.session = session;
            this.httpSession = httpSession;
        }else{
            //用户未登陆
            try {
                session.close();
            } catch (IOException e) {
                e.printStackTrace();

这下我们就从java的webscoket中拿到了用户的session。

按上面流程操作的时候出现了HttpSession为空,获取不到用户信息的情况

在前端连接WebSocket的时候,我的代码是这样的:

var url = "ws://127.0.0.1/XXXX/XXXX";

然而浏览器地址栏是这样的:

http://localhost:8080/

网上解释说如果不使用同一个host,则会创建不同的连接请求。在没有HttpSession激活状态的时候,使用getSession()方法会新建一个HttpSession,也就是说实际上这个监听器是在没有激活HttpSession的情况下不断新建会话。 
于是我们可以这样拼接一下:

    var host = window.location.host;
    var url = "ws://"+host+"/XXXX/XXXX";

这样就可以正常的获取到用户的session了

如果对spring webSocket 的基本使用方式不了解,请参考:webSocket实现扫码登录 握手拦截器:HttpSessionHandshakeInterceptor的实现类重写beforeHandshake方法,如下 @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttp... 一:本文适用范围 本文使用J2EE规范原生的WebSocket开发,适用于项目WebSocket的使用比较少的情况,而Spring WebSocket封装的比较繁重,反而不适用于实际项目的情况。 自己在开发时就是由于项目的原因,不想用Spring-WebSocket,所有用了原生的,但是找了好多帖子试了好多方法都不行,甚至将原生的和Spring-WebSocket混用了也是不行,看源... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 服务端配置 @Config. websocket使用握手拦截器 public class HttpSessionHandshakeInterceptor extends org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, W 获取用户角色和权限 若依系统每次跳某个页面时,都会调用该方法,以检查当前用户是否有权限访问该页面,或者说该页面能基于该用户的角色身份和权限来显示菜单数量。 老办法前端用f12网络活动来获取后台请求路径。 该方法主要作用是: 1,从springsecurity获取当前登录用户的信息 2,根据当前用户信息来查询当前用户的角色集合 3,根据当前用户信息来查询当前用户的权限集合 4,将以上信息放入AjaxResult返回给前端。 springsecurity是如何获取当前用户信息的呢? 这里封装了一个安全服务 1、什么是WebSocket WebSocket 是一种自然的全双工、双向、单套接字连接。使用WebSocket,你的HTTP 请求变成打开WebSocket 连接(WebSocket 或者WebSocket over TLS(TransportLayer Security,传输层安全性,原称“SSL”))的单一请求,并且重用从客户端到服务器以及服务器到客户端的同一连接。WebSocket 减少... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> springboot要添加两个配置,正常情况下一个配置也可以,但是使用 @Autowired注入的servi import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; impor