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