spring websocket 接口 校验token问题 - Go语言中文社区

spring websocket 接口 校验token问题


spring 集成websocket,接口校验token时,遇到个问题,记录一下;

1、前端JS跨域请求,携带cookie,校验token,

      Android手机端,有的内置浏览器,跨域请求,携带cookie失效,导致无法校验token;

浏览器同源策略:

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

https://www.cnblogs.com/laixiangran/p/9064769.html

 

登录地址:https://www.test.com/account/login.action           登录成功后,服务器返回响应,cookie中设置token信息

websocket地址:wss://www.test.com/websocket/monitor       ==》不同源

使用HBuilder开发APP,发现,在有些Android手机上,不能正常携带cookie信息(包含token);

(PC 浏览器端,同样的JS代码,携带cookie信息正常,暂时没有发现问题;)

所以,参考网上的解决方法,移动端,在请求websocket地址后,添加参数:

                             wss://www.test.com/websocket/monitor?token=xxxxxxxxxx

对于服务端,则支持两种方式请求:1、优先从cookie中获取token;

                                                          2、如果cookie中没有token,则从请求参数中获取token;

                                                          3、否则,无token;

 

2、服务端校验token的时机

2.1、在websocket handshake 校验

WebSocketConfig.java 配置

@Configuration
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
	private static final Logger logger = Logger.getLogger(WebSocketConfig.class);

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		logger.info("registerWebSocketHandlers");
		// 前台 可以使用websocket环境
		registry.addHandler(newWebSocketHandler(), "/websocket/monitor")
				.setHandshakeHandler(newHandshakeHandler())
				.setAllowedOrigins("*")// 允许跨域
				.addInterceptors(newHandshakeInterceptor());
		// 前台 不可以使用websocket环境,则使用sockjs进行模拟连接
		registry.addHandler(newWebSocketHandler(), "/sockjs/websocket")
				.setAllowedOrigins("*")
				.addInterceptors(newHandshakeInterceptor())
				.withSockJS();
	}

	public WebSocketHandler newWebSocketHandler() {
		return new MyWebSocketHandler();
	}

	public HandshakeInterceptor newHandshakeInterceptor() {
		return new WebSocketHandshakeInterceptor();
	}

	public HandshakeHandler newHandshakeHandler() {
		return new MyHandshakeHandler();
	}
}

 

public class WebSocketHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
	private static final Logger LOGGER = Logger.getLogger(WebSocketHandshakeInterceptor.class);

	@Override
	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes)
			throws Exception {
		HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
		String keyName = "token";
		// 1.从cookie中获取token
		String token = CookieUtils.getCookieValue(servletRequest, keyName);
		if (!StringUtils.isEmpty(token)) {
			// 2.从请求参数中获取token
			token = servletRequest.getParameter(keyName);
		}

		// 3.校验token
		if (StringUtils.isEmpty(token)) {
			LOGGER.error("no user token, refuse connect");
			response.setStatusCode(HttpStatus.FORBIDDEN);
			// attributes.put("selfErrorCode", 10000009);
			return false;
		}

		return super.beforeHandshake(request, response, webSocketHandler, attributes);
	}

	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception e) {
		super.afterHandshake(request, response, wsHandler, e);
	}
}

但是,使用该种方法,前端被拒绝后,JS代码 WebSocket.onerror,WebSocket.onclose 回调方法,都无法获取到对应的错误码,进而无法区分错误;

(TODO 也有可能是方法不对吧,有空再找找原因)

 

2.2、在websocket handshake 校验

MyHandshakeHandler.java,握手时校验,继承自DefaultHandshakeHandler

                                                                   或者,也可以自己实现接口 HandshakeHandler ,实现 doHandshake 方法

public class MyHandshakeHandler extends DefaultHandshakeHandler {
	private static final Logger LOGGER = Logger.getLogger(MyHandshakeHandler.class);

	@Override
	protected boolean isValidOrigin(ServerHttpRequest request) {
		HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
		String keyName = "token";
		// 1.从cookie中获取token
		String token = CookieUtils.getCookieValue(servletRequest, keyName);
		if (!StringUtils.isEmpty(token)) {
			// 2.从请求参数中获取token
			token = servletRequest.getParameter(keyName);
		}

		// 3.校验token
		if (StringUtils.isEmpty(token)) {
			LOGGER.error("no user token, refuse connect");
			return false;
		}

		return super.isValidOrigin(request);
	}
}

在AbstractHandshakeHandler的doHandshake方法中,会调用 isValidOrigin 方法,如果校验token失败,则会返回 FORBIDDEN 403 错误;

/**
 * Return whether the request {@code Origin} header value is valid or not.
 * By default, all origins as considered as valid. Consider using an
 * {@link OriginHandshakeInterceptor} for filtering origins if needed.
 */
protected boolean isValidOrigin(ServerHttpRequest request) {
   return true;
}
/**
 * {@code 403 Forbidden}.
 * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.3">HTTP/1.1: Semantics and Content, section 6.5.3</a>
 */
FORBIDDEN(403, "Forbidden"),

同上,前端JS无法获取错误码;

 

2.3、在websocket handshake 后 ,建立连接时 校验 (有点晚了)

MyWebSocketHandler.java,在 afterConnectionEstablished 方法中校验;

这种方式,前端JS,WebSocket.onclose 可以获取错误码

不过,体验上不太好(先连接成功,然后断开);

public class MyWebSocketHandler implements WebSocketHandler {
	private static final Logger LOGGER = Logger.getLogger(MyWebSocketHandler.class);

	private static final List<WebSocketSession> sessionList = new LinkedList<>();
	private static final List<String> tokenList = new LinkedList<>();

	private String getTokenFromQueryParams(WebSocketSession session) {
		String queryParams = session.getUri().getQuery();
		if (StringUtils.isEmpty(queryParams)) {
			return null;
		}
		// 测试,只有一个参数
		String[] array = queryParams.split("=");
		if (array.length == 2) {
			return array[1];
		}
		return null;
	}

	private String getTokenFromCookie(WebSocketSession session) {
		HttpHeaders httpHeaders = session.getHandshakeHeaders();
		return CookieUtils.getCookieValue(httpHeaders, "token");
	}

	private String getTokenBySession(WebSocketSession session) {
		String token = getTokenFromCookie(session);
		if (!StringUtils.isEmpty(token)) {
			return token;
		}
		return getTokenFromQueryParams(session);
	}

	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		String token = getTokenBySession(session);
		if (StringUtils.isEmpty(token)) {
			LOGGER.error("token is empty");
			session.close(new CloseStatus(4500));
			return;
		}

		// 添加session
		synchronized (sessionList) {
			sessionList.add(session);
		}
	}

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yhtppp/article/details/88549549
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-08 10:33:49
  • 阅读 ( 1005 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢