Spring Boot中@ServerEndpoint的onOpen无法直接获取客户端IP,因WebSocket升级后脱离HTTP上下文;需在握手阶段通过过滤器存IP至HttpSession,再经自定义Configurator注入EndpointConfig,最后在onOpen中取出,或解析X-Forwarded-For并过滤私有地址。

Spring Boot 里用 @ServerEndpoint 写的 WebSocket,onOpen 方法里直接调 session.getBasicRemote() 或 session.getAsyncRemote() 都拿不到客户端 IP —— 这不是你代码写错了,是标准 API 压根没暴露这个信息。必须绕道 HTTP 握手阶段或容器底层才能拿到真实 IP。
为什么 session.getRemoteAddress() 在 Spring Boot + Tomcat 下经常返回 null 或 127.0.0.1
WebSocket 连接建立前会先发一个 HTTP Upgrade 请求(即握手),此时 Servlet 容器能拿到完整的 HttpServletRequest;但一旦升级完成,后续通信就脱离 HTTP 上下文,Session 对象内部不再持有原始请求对象。Tomcat 默认的 session.getRemoteAddress() 只在极少数配置下有效,且不处理代理转发场景。
- 没配
RemoteIpValve时,Nginx/SLB 转发后看到的是代理 IP,不是真实客户端 IP - Spring Boot 内嵌 Tomcat 默认不启用
RemoteIpValve,需手动配置 -
session.getUserProperties()里查不到javax.websocket.server.ServerEndpointConfig.MESSAGE_CONTEXT—— 因为 Spring 的ServerEndpointExporter没传这个上下文
通过 HandshakeRequest 拿 HttpSession 再取 IP(推荐用于有登录态场景)
核心思路:在 WebSocket 握手时(HTTP 阶段),把 HttpServletRequest.getRemoteAddr() 或更可靠的 X-Forwarded-For 解析结果存进 HttpSession,再通过自定义 Configurator 注入到 EndpointConfig 中,最终在 onOpen 里取出。
- 写一个继承
ServerEndpointConfig.Configurator的类,重写modifyHandshake - 在方法里从
HandshakeRequest取getHttpSession(),再调getAttribute("user_ip") - 把 IP 存进
sec.getUserProperties(),比如键名为"client_ip" - @ServerEndpoint 注解加上
configurator = YourConfigurator.class -
@OnOpen方法签名加EndpointConfig config参数,再用config.getUserProperties().get("client_ip")
注意:getRemoteAddr() 在 Nginx 后面仍是代理 IP,得自己解析 request.getHeader("X-Forwarded-For"),取第一个非私有地址(如 10.x、172.16–31.x、192.168.x)。
用反射直接读 Tomcat 的 channel(仅限内网、可控环境)
如果你确认运行在 Tomcat 8.5+ 且没加代理,可走反射路径读底层 AsyncRemote 关联的 InetSocketAddress。这方法快、不依赖 HTTP Session,但属于“碰运气”——不同容器(Jetty/Undertow)字段名和结构完全不同,升级 Tomcat 小版本也可能失效。
由 ReMe 驱动的记忆管理系统,支持跨会话记忆持久化、自动应用用户偏好以及智能上下文压缩...
- 只对 Tomcat 有效,
session.getAsyncRemote()是入口点 - 字段链类似:
async → base → sos → socketWrapper → socket → sc → remoteAddress - 必须 catch
NoSuchFieldException和IllegalAccessException - 返回
InetSocketAddress,调.getAddress().getHostAddress()才是字符串 IP
别在生产环境无防护地用这个,它绕过了所有安全层,且无法识别 X-Real-IP 等代理头。
Spring Boot + Netty 场景下用 OGNL 表达式取 channel.remoteAddress
如果你用的是 netty-websocket-spring-boot-starter(非 Spring 官方 WebSocket),底层是 Netty Channel,那么 Session 对象实际包装了 Channel。这时可以用 OGNL 表达式安全访问:
- 表达式写成
#root.channel.remoteAddress.getAddress().getHostAddress() - 需要引入
ognl依赖,并设置OgnlContext.setMemberAccess(new DefaultMemberAccess(true)) - 比反射稳定,因为 Netty 的
Channel结构比 Tomcat 内部字段规范得多 - 仍要处理
NullPointerException:channel 可能未初始化或已关闭
这个方案只适用于明确使用 Netty 作为 WebSocket 底层的项目,Spring 官方 starter 不支持。
最易踩坑的其实是代理头解析逻辑 —— 很多人直接 request.getHeader("X-Forwarded-For").split(",")[0].trim(),却没过滤掉伪造的私有网段地址。真实生产环境里,IP 获取必须结合 RemoteIpValve 配置、可信代理白名单、多级头校验三者才稳妥。

















