问题背景
在18年工作的时候遇到一个WebSocket连接通过nginx转发后死活获取不到客户端真实ip的问题,在重新学习WebSocket后对于WebSocket的认识更加深入,遂总结下,方便大家交流沟通。
WebSocket协议
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
其中握手的动作是HTTP协议,进行协议升级后,建立WebSocket连接(建议多读几遍)
问题分析
如果在Websocket连接建立之后在获取ip,能够获取的信息是有限的,因为socket连接本身就获取不到什么信息.在nginx中转后,你建立的socket连接是和nginx建立的.所以ip获取的是127.0.0.1。
核心 通过在握手的第一步进行 鉴权
获取ip
获取Session中的信息
下面是简单的WebSocket连接,支持spring bean
import org.springframework.session.Session;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Optional;
/**
* @author wangqimeng
* @date 2020/3/25 下午9:53
*/
public class HttpSessionConfigurator extends SpringBootConfigurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
//如果header中没有token字段 校验失败.
//如果会话过期 校验失败...
if (request.getHttpSession() instanceof Session) {
//认为会话是有效的
//然后讲Session中的信息取出,进行认证.
}
//这块就可以通过header进行认证了
sec.getUserProperties().put("X-Real-Ip", Optional.ofNullable(request.getHeaders().get("X-Real-Ip")));
}
}
import cn.boommanpro.tomato.web.context.ApplicationContextRegister;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.socket.server.standard.SpringConfigurator;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* copy from {@link SpringConfigurator}
*
* @author wangqimeng
* @date 2020/3/25 下午11:07
*/
public class SpringBootConfigurator extends ServerEndpointConfig.Configurator {
private static final String NO_VALUE = ObjectUtils.identityToString(new Object());
private static final Log logger = LogFactory.getLog(SpringConfigurator.class);
private static final Map<String, Map<Class<?>, String>> cache =
new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
ApplicationContext context = ApplicationContextRegister.getApplicationContext();
if (context == null) {
String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?";
logger.error(message);
throw new IllegalStateException(message);
}
String beanName = ClassUtils.getShortNameAsProperty(endpointClass);
if (context.containsBean(beanName)) {
T endpoint = context.getBean(beanName, endpointClass);
if (logger.isTraceEnabled()) {
logger.trace("Using @ServerEndpoint singleton " + endpoint);
}
return endpoint;
}
Component ann = AnnotationUtils.findAnnotation(endpointClass, Component.class);
if (ann != null && context.containsBean(ann.value())) {
T endpoint = context.getBean(ann.value(), endpointClass);
if (logger.isTraceEnabled()) {
logger.trace("Using @ServerEndpoint singleton " + endpoint);
}
return endpoint;
}
beanName = getBeanNameByType(context, endpointClass);
if (beanName != null) {
return (T) context.getBean(beanName);
}
if (logger.isTraceEnabled()) {
logger.trace("Creating new @ServerEndpoint instance of type " + endpointClass);
}
return context.getAutowireCapableBeanFactory().createBean(endpointClass);
}
@Nullable
private String getBeanNameByType(ApplicationContext context, Class<?> endpointClass) {
String contextId = context.getId();
Map<Class<?>, String> beanNamesByType = cache.get(contextId);
if (beanNamesByType == null) {
beanNamesByType = new ConcurrentHashMap<>();
cache.put(contextId, beanNamesByType);
}
if (!beanNamesByType.containsKey(endpointClass)) {
String[] names = context.getBeanNamesForType(endpointClass);
if (names.length == 1) {
beanNamesByType.put(endpointClass, names[0]);
} else {
beanNamesByType.put(endpointClass, NO_VALUE);
if (names.length > 1) {
throw new IllegalStateException("Found multiple @ServerEndpoint's of type [" +
endpointClass.getName() + "]: bean names " + Arrays.asList(names));
}
}
}
String beanName = beanNamesByType.get(endpointClass);
return (NO_VALUE.equals(beanName) ? null : beanName);
}
}
import cn.boommanpro.tomato.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
/**
* @author wangqimeng
* @date 2020/3/25 下午8:49
*/
@Slf4j
@Component
@ServerEndpoint(value = "/websocket", configurator = HttpSessionConfigurator.class)
public class WebSocketPractice {
private final SysUserService sysUserService;
public WebSocketPractice(SysUserService sysUserService) {
this.sysUserService = sysUserService;
}
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
log.info("客户端ID:[{}],onOpen 建立成功", session.getId());
}
@OnClose
public void onClose(Session session) {
log.info("客户端ID:[{}],onClose 连接关闭", session.getId());
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("客户端ID:[{}],收到客户端消息:{}", session.getId(), message);
}
@OnError
public void onError(Session session, Throwable t) {
log.error("Websocket Error 客户端ID:" + session.getId(), t);
}
}