之前有过两篇博文是有关WebSocket身份认证的
关于使用浏览器与PostMan测试springSession每次返回的x-auto-token不一致的问题解决
当时是处于一个探索的环节,现在我们将使用SpringSession、Redis,通过Header认证来实现WebSocket中用户身份的认证。
注意:为了简化文章,我们假设用户已经是通过登录并且获得x-auth-token
值的。下文中不会给出的内容有:redis的配置、
首先看下我们的Redis设置,里面涉及到了一个RedisTemplate类的注册:
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
<T> RedisTemplate<String, User> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
System.out.println("加载redis配置");
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(User.class.getClassLoader());
redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
redisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
}
通过RedisTemplate
的注册,我们可以使用redisTemplate
这个实例来操作Redis数据库。并且我们使用的是jdk反序列化(因为SpringSession
是使用jdk
序列化的,我们需要使用该实例来获得SpringSession
存放在Redis中的数据,使用其他反序列化会导致乱码)。
WebSocket的配置:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private int loss_connect_time = 0;
@Autowired
private WebSocketHandler handler;
@Autowired
private WebSocketInterceptor webSocketInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/message").addInterceptors(webSocketInterceptor).setAllowedOrigins("*");
registry.addHandler(handler, "/sockjs/message").addInterceptors(webSocketInterceptor).setAllowedOrigins("*").withSockJS();
}
}
此处的WebSocketHandler
是用以处理WebSocket连接的处理器,我们将在这里进行WebSocket数据的接收处理及转发,而webSocketInterceptor
则是一个Websocket连接的拦截器,通过该拦截器我们可以对用户的身份进行认证,决定其是否能够建立WebSocket连接。
在registerWebSocketHandlers
中我们注册了WebSocket连接的接口,使用ws://localhost:port/message
就可以建立WebSocket连接了。
首先让我们看一下webSocketInterceptor
:
@Service
@Slf4j
public class WebSocketInterceptor implements HandshakeInterceptor {
@Resource
private RedisTemplate<String, User> redisTemplate;
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
WebSocketHandler webSocketHandler, Map<String, Object> map) {
log.info("webSocket握手请求...");
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
// 取得x-auth-token参数
String token = servletRequest.getServletRequest().getHeader("x-auth-token");
log.info("websocket连接令牌" + token);
if (null != token && !token.equals("")) {
// 利用SpringSession在Redis数据库中的存放方式读取user对象
User user = (User) redisTemplate.opsForHash().get("spring:session:sessions:" + token, "sessionAttr:user");
// 此处可以进行用户权限的判定,从而决定是否允许建立WebSocket连接
if (null != user) {
// 判断user对象
log.info("user用户:" + user);
// 将user对象于某一唯一标志绑定放入map中
// 我们可以在后续的WebSocketHandler操作中使用到user的值
map.put(serverHttpRequest.getRemoteAddress().toString(), user);
return true;
}
}
}
return false;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
WebSocketHandler webSocketHandler, Exception e) {
log.info("webSocket握手结束...");
}
}
在beforeHandshake()
中返回true即为通过验证,允许建立连接,反之不允许。
在实际操作中,我们会与接口ws://localhost:port/message?x-auth-token=8ea455a8-7f61-45af-ae20-e5627ee89f15
建立连接,传入的x-auth-token
即为SpringSession的唯一标志,并且对应着Redis数据库中spring:session:sessions:
后续的一串字母。有了x-auth-token
。
到了这里还是要讲一下x-auth-token
是怎么来的。在SpringSession的设置中:
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1801)
public class HttpSessionConfig {
@Bean
public HeaderHttpSessionIdResolver httpSessionStrategy() {
return new HeaderHttpSessionIdResolver("x-auth-token");
}
}
通过HeaderHttpSessionIdResolver
可以设置SpringSession为Header认证策略,同时设置其指定的头部信息为x-auth-token
。这样,当用户每次操作新的Session时,都会使用x-auth-token
返回一个新的Session标志,同时,我们设置头部信息x-auth-token=asd
就可以拿到相对于的Session。这种方法避免了用户禁用Cookie导致Session不可用的情况的发生。
到了此处就已经是WebSocket的用户认证的整个过程了,不过大家可能还记得我们在WebSocketInterceptor
中往map
存入用户信息的操作吧。那么我们接下来我们就可以在WebSocketHandler
中使用用户信息了:
@Service
@Slf4j
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
log.info("成功建立连接");
}
@Override
public void handleMessage(WebSocketSession webSocketSession,
WebSocketMessage<?> webSocketMessage) throws Exception {
String message = webSocketMessage.getPayload().toString();
log.info("接收信息 >> {}", message);
// 此处拿到刚才存放的用户信息
User user = (User) webSocketSession.getAttributes() .get(String.valueOf(webSocketSession.getRemoteAddress()));
}
@Override
public void handleTransportError(WebSocketSession webSocketSession,
Throwable throwable) throws Exception {
log.info("{}连接出现异常", webSocketSession.getId());
}
@Override
public void afterConnectionClosed(WebSocketSession webSocketSession,
CloseStatus closeStatus) throws Exception {
log.info("Socket会话结束,即将移除socket");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
总的来说WebSocket的身份认证还是建立在Http的身份认证之上的,通过Http用户进行登陆后存储用户的基本信息,此时我们就可以通过x-auth-token
取得用户的Session,对Session里面的数据进行验证,判断是否有权限建立WebSocket连接,若通过之后我们又可以通过Map数据的存放,在WebSocketHandler中也可以使用用户的信息。这样就能够跨越Http与WebSocket之间的阻碍了。