Redis的應用の使用SpringSession實現WebSocket的用戶身份認證

之前有過兩篇博文是有關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之間的阻礙了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章