Redis的应用の使用SpringSession实现WebSocket的用户身份认证

    xiaoxiao2021-04-17  268

    之前有过两篇博文是有关WebSocket身份认证的

    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之间的阻碍了。


    最新回复(0)