SpringBoot integrates WebSocket (get the real ip address of the client)

Posted by alex_savin on Sat, 22 Jan 2022 21:19:24 +0100

When you encounter the demand of "background push", you can't avoid websocket. This time, the demand is a little special. The ip of the client is fixed. You need to distinguish the specific client according to the ip of the client.

However, for future use, I also list another way to obtain the identity of the connected user - the url parameter.

It should be noted that if your project adds an interceptor or or uses a security framework such as security, you need to release the path of websocket, otherwise it will always report connection failure.

catalogue

rely on

configuration file

Main class

Scheme 1: URL authentication

Scheme 2: ip authentication

1. Define an interceptor

2. Define WebSocketConfigurator

3. Define principal class

4. Interceptor scan

rely on

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.4.5</version>
        </dependency>

configuration file

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

Main class

The main class manages the connection of websocket. The following two schemes are given:

Scheme 1: URL authentication

The following userNo can be replaced with a token in the actual project and authenticated in onOpen.

@Slf4j
@Component
@ServerEndpoint(value = "/websocket/{userNo}")
public class WebSocketServer {

    private static int onlineCount = 0;

    private static ConcurrentHashMap<String, WebSocketServer> serverMap = new ConcurrentHashMap<>();

    private Session session;

    private String userNo;

    @OnOpen
    public void onOpen(Session session, @PathParam("userNo") String userNo) {
        this.session = session;
        this.userNo = userNo;
        if(serverMap.containsKey(userNo)) {
            serverMap.remove(userNo);
            serverMap.put(userNo, this);
        }else {
            serverMap.put(userNo, this);
            addOnlineCount();
            log.info(userNo + ",Online!");
        }
    }

    /**
     * The server receives messages from the client
     * @param message news
     * @param session Session session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("The server received a user request" + userNo + "Message from:" + message);
    }

    /**
     * The server actively sends messages
     * @param message news
     */
    public void sendMessage(String message){
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * Get online people
     * @return Number of people online
     */
    public static int getOnlineCount() {
        return onlineCount;
    }

    @OnClose
    public void onClose() {
        if(serverMap.containsKey(userNo)) {
            serverMap.remove(userNo);
            subOnlineCount();
            log.info(userNo + ",Offline!");
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("user" + userNo + "An error occurred as follows:" + throwable.getMessage());
    }

    private static synchronized void subOnlineCount() {
        onlineCount--;
    }

    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    public static WebSocketServer get(String userNo) {
        return serverMap.get(userNo);
    }

    public static ConcurrentHashMap<String, WebSocketServer> getMap() {
        return serverMap;
    }

    public static boolean isOnline(String userNo) {
        return serverMap.containsKey(userNo);
    }
}

Scheme 2: ip authentication

It is suitable for Intranet environment where the number of hosts is fixed and the ip address is fixed.

WebSocket is just a protocol. There are many ways to implement this protocol.

The starter we use cannot directly obtain the client ip. Many people on the Internet use the netty websocket XX package, which provides an api to obtain the client ip.

Changing the package is too troublesome. We can solve this problem even without changing the package.

1. Define an interceptor

This interceptor is used to obtain the ip and put it into the session

@javax.servlet.annotation.WebFilter(filterName = "sessionFilter",urlPatterns = "/*")
@Order(1)
public class WebFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req= (HttpServletRequest) servletRequest;
        req.getSession().setAttribute("ip",req.getRemoteHost());
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2. Define WebSocketConfigurator

It is used to pass the ip of the client to the session in websocket, which is equivalent to an intermediary

public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {

    public static final String IP_ADDR = "IP.ADDR";

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

        Map<String, Object> attributes = sec.getUserProperties();
        HttpSession session = (HttpSession) request.getHttpSession();
        if (session != null) {
            attributes.put(IP_ADDR, session.getAttribute("ip"));
            Enumeration<String> names = session.getAttributeNames();
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                attributes.put(name, session.getAttribute(name));
            }
        }
    }
}

3. Define principal class

The principal class is used to manage websocket connections and configure the configurator

@Slf4j
@Component
@ServerEndpoint(value = "/websocket",configurator = WebSocketConfigurator.class)
public class WebSocketServer {

    private static int onlineCount = 0;

    private static ConcurrentHashMap<String, WebSocketServer> serverMap = new ConcurrentHashMap<>();

    private Session session;
    
    private String ipAddr;

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        Map<String, Object> userProperties = session.getUserProperties();
        this.ipAddr = (String) userProperties.get(WebSocketConfigurator.IP_ADDR);
        if(serverMap.containsKey(this.ipAddr)) {
            serverMap.remove(this.ipAddr);
            serverMap.put(this.ipAddr, this);
        }else {
            serverMap.put(this.ipAddr, this);
            addOnlineCount();
            log.info(this.ipAddr + ",Online!");
        }
    }

    /**
     * The server receives messages from clients
     * @param message news
     * @param session Session session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("The server received a user request" + ipAddr + "Message from:" + message);
        
        //Convenient front-end test
        sendMessage("The server received a user request" + ipAddr + "Message from:" + message);
    }

    /**
     * Send a message to the client with ip address ipAddr
     * @param ipAddr ip address
     * @param message news
     */
    public static void sendMessage(String ipAddr, String message) {
        if(serverMap.containsKey(ipAddr)) {
            WebSocketServer webSocketServer = serverMap.get(ipAddr);
            webSocketServer.sendMessage(message);
        }else {
            log.error("Sending failed, client not connected: " + ipAddr);
        }
    }
    
    /**
     * The server actively sends messages
     * @param message news
     */
    public void sendMessage(String message){
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * Get online people
     * @return Number of people online
     */
    public static int getOnlineCount() {
        return onlineCount;
    }

    @OnClose
    public void onClose() {
        if(serverMap.containsKey(ipAddr)) {
            serverMap.remove(ipAddr);
            subOnlineCount();
            log.info(ipAddr + ",Offline!");
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("user" + ipAddr + "An error occurred as follows:" + throwable.getMessage());
    }

    private static synchronized void subOnlineCount() {
        onlineCount--;
    }

    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    public static WebSocketServer get(String ipAddr) {
        return serverMap.get(ipAddr);
    }

    public static ConcurrentHashMap<String, WebSocketServer> getMap() {
        return serverMap;
    }

    public static boolean isOnline(String ipAddr) {
        return serverMap.containsKey(ipAddr);
    }

}

4. Interceptor scan

If there is only the above code, the tomcat session cannot be obtained because the interceptor does not take effect.

Add an annotation @ ServletComponentScan on the SpringBoot startup class:

@EnableScheduling
@SpringBootApplication
@ServletComponentScan("The package name of the interceptor")
public class WmsServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(WmsServerApplication.class, args);
    }

}

Topics: Java Spring Boot websocket