前言相对于Http单一的通信方式,WebSocket可以主动从服务端向浏览器推送消息。该功能可以帮助我们完成订单消息推送、IM实时聊天等特定业务。但是WebSocket本身并没有直接支持“身份认证”,客户端连接默认是“欢迎来者不拒”,所以认证授权需要我们自己来做。Sa-Token是一个java权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关认证等一系列与权限相关的问题。GitHub开源地址:https://github.com/dromara/sa-token下面介绍如何在WebSocket中集成Sa-Token认证,保证连接的安全。两种集成方式我们将依次介绍最常见的两种WebSocket集成方式:Java原生版:javax.websocket.SessionSpring封装版本:WebSocketSession废话不多说,直接上手:方法一:Java原生版javax.websocket。Session1,首先是引入pom.xml依赖org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-websocketcn.dev33sa-token-spring-boot-starter1.29.02、登录接口,用于获取sessiontoken/***登录测试*/@RestController@RequestMapping("/acc/")publicclassLoginController{//测试登录----http://localhost:8081/acc/doLogin?name=zhang&pwd=123456@RequestMapping("doLogin")publicSaResultdoLogin(Stringname,Stringpwd){//这只是一个模拟例子,实际项目需要从数据库中查询数据进行比较if("zhang".equals(名称)&&"123456".equals(pwd)){StpUtil.login(10001);returnSaResult.ok("登录成功").set("token",StpUtil.getTokenValue());}returnSaResult.error("登录失败");}//...}3。WebSocket连接处理@Component@ServerEndpoint("/ws-connect/{satoken}")publicclassWebSocketConnect{/***固定前缀*/privatestaticfinalStringUSER_ID="user_id_";/***存放Session集合,方便推送消息(javax.websocket.Session)*/privatestaticConcurrentHashMapsessionMap=newConcurrentHashMap<>();//监听:连接成功@OnOpenpublicvoidonOpen(Sessionsession,@PathParam("satoken")Stringsatoken)throwsIOExceptionion{//根据token获取对应的userIdObjectloginId=StpUtil.getLoginIdByToken(satoken);如果(loginId==null){session.close();thrownewSaTokenException("连接失败,令牌无效:"+satoken);}//放入集合,方便后续操作longuserId=SaFoxUtil.getValueByType(loginId,long.class);sessionMap.put(USER_ID+userId,session);//提示Stringtips="Web-Socket连接成功,sid="+session.getId()+",userId="+userId;System.out.println(提示);sendMessage(会话,提示);}//监听:连接关闭@OnClosepublicvoidonClose(Sessionsession){System.out.println("Connectionclosed,sid="+session.getId());for(Stringkey:sessionMap.keySet()){if(sessionMap.get(key).getId().equals(session.getId())){sessionMap.remove(key);}}}}//监听器:接收来自客户端@OnMessage的消息publicvoidonMessage(Sessionsession,Stringmessage){System.out.println("sidis:"+session.getId()+",sent:"+message);}//监听:发生异常@OnErrorpublicvoidonError(Sessionsession,Throwableerror){System.out.println("sidis:"+session.getId()+",anerroroccurred");错误.printStackTrace();}//--------//向指定客户端推送消息publicstaticvoidsendMessage(Sessionsession,Stringmessage){try{System.out.println("Thesidis:"+session.getId()+",发送:"+消息);session.getBasicRemote().sendText(消息);}catch(IOExceptione){thrownewRuntimeException(e);}}//向指定用户推送消息publicstaticvoidsendMessage(longuserId,Stringmessage){Sessionsession=sessionMap.get(USER_ID+userId);如果(会话!=null){sendMessage(会话,消息);}}}4。WebSocket配置/***启用WebSocket支持*/@ConfigurationpublicclassWebSocketConfig{@BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}}5.启动类@SpringBootApplicationpublicclassSaTokenWebSocketApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SaTokenWebSocketApplication.class,args);}}搭建完成后,启动项目6.测试1.首先我们访问登录界面,获取sessiontokenhttp://localhost:8081/acc/doLogin?name=zhang&pwd=123456,如图:2、然后我们可以找一个WebSocket在线测试页面进行连接,例如:https://www.bejson.com/httputil/websocket/连接地址:ws://localhost:8081/ws-connect/302ee2f8-60aa-42aa-8ecb-eeae5ba57015如图:3.如果我们输入了错误的token,会发生什么?如您所见,连接将立即断开!方法二:Spring封装版本:WebSocketSession1,同上:先引入pom.xml依赖org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-websocketcn.dev33sa-token-spring-boot-starter1.29.02.登录接口,用于获取会话令牌/***登录测试*/@RestController@RequestMapping("/acc/")publicclassLoginController{//测试登录----http://localhost:8081/acc/doLogin?name=zhang&pwd=123456@RequestMapping("doLogin")publicSaResultdoLogin(Stringname,Stringpwd){//这只是一个模拟示例,真实项目需要从数据库中检索查询数据进行比较if("zhang".equals(name)&&"123456".equals(pwd)){StpUtil.login(10001);returnSaResult.ok("登录成功").set("token",StpUtil.getTokenValue());}returnSaResult.error("登录失败");}//...}3、WebSocket连接处理/***处理WebSocket连接*/publicclassMyWebSocketHandlerextendsTextWebSocketHandler{/***固定前缀*/privatestaticfinalStringUSER_ID="user_id_";/***存放Session集合,方便推送消息*/privatestaticConcurrentHashMapwebSocketSessionMaps=newConcurrentHashMap<>();//监听:连接开启@OverridepublicvoidafterConnectionEstablished(WebSocketSessionsession)throwsException{//放入集合,方便后续操作StringuserId=session.getAttributes().get("userId").toString();webSocketSessionMaps.put(USER_ID+userId,session);//提示Stringtips="Web-Socket连接成功,sid="+session.getId()+",userId="+userId;System.out.println(提示);sendMessage(会话,提示);}//侦听器:连接关闭@OverridepublicvoidafterConnectionClosed(WebSocketSessionsession,CloseStatusstatus)throwsException{//从集合中删除字符串userId=session.getAttributes().get("userId").toString();webSocketSessionMaps.remove(USER_ID+userId);//给出提示Stringtips="Web-Socketconnectionclosed,sid="+session.getId()+",userId="+userId;System.out.println(提示);}//接收消息@OverridepublicvoidhandleTextMessage(WebSocketSessionsession,TextMessagemessage)throwsIOException{System.out.println("sidis:"+session.getId()+",sent:"+message);}//-----------//向指定客户端推送消息publicstaticvoidsendMessage(WebSocketSessionsession,Stringmessage){try{System.out.println("Thesidis:"+session.getId()+",发送:"+消息);会议。发送消息(新文本消息(消息));}catch(IOExceptione){抛出新的RuntimeException(e);}}//向指定用户推送消息publicstaticvoidsendMessage(longuserId,Stringmessage){WebSocketSessionsession=webSocketSessionMaps.get(USER_ID+userId);如果(会话!=null){sendMessage(会话,消息);}}}4.WebSocket前置拦截器/***WebSocket握手前置拦截器*/publicclassWebSocketInterceptorimplementsHandshakeInterceptor{//握手前触发(返回true表示握手成功)@OverridepublicbooleanbeforeHandshake(ServerHttpRequestrequest,ServerHttpResponseresponse,WebSocketHandlerhandler,Mapattr){System.out.println("----握手前触发"+StpUtil.getTokenValue());//如果未登录则拒绝握手if(StpUtil.isLogin()==false){System.out.println("----未经授权的客户端,连接失败");返回假;}//标记userId,握手成功attr.put("userId",StpUtil.getLoginIdAsLong());返回真;}//@OverridepublicvoidafterHandshake(ServerHttpRequestrequest,ServerHttpResponseresponse,WebSocketHandlerwsHandler,Exceptionexception){System.out.println("----握手后触发");}}5。WebSocket配置/***WebSocket相关配置*/@Configuration@EnableWebSocketpublicclassWebSocketConfigimplementsWebSocketConfigurer{//注册WebSockethandler@OverridepublicvoidregisterWebSocketHandlers(WebSocketHandlerRegistrywebSocketHandlerRegistry){webSocketHandlerRegistry//WebSocket连接handler.addHandler(newMyWebSocketHandler(),"/ws-connect")//WebSocketinterceptor.addInterceptors(newWebSocketInterceptor())//允许跨域.setAllowedOrigins("*");}}6.启动类/***Sa-Token集成WebSocket认证实例*/@SpringBootApplicationonpublicclassSaTokenWebSocketSpringApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SaTokenWebSocketSpringApplication.class,args);}}启动项目,开始测试7,测试1,首先访问登录界面,获取sessiontokenhttp://localhost:8081/acc/doLogin?name=zhang&pwd=123456如图:2.然后打开WebSocket在线测试页面进行连接,例如:https://www.bejson.com/httputil/websocket/连接地址:ws://localhost:8081/ws-connect?satoken=fe6e7dbd-38b8-4de2-ae05-cda7e36bf2f7如图:注意:这里使用url传递Token,因为在第三方测试页面上比较方便。实际项目中可以通过Cookie下载,Header参数,url参数可以选择三种方式中的一种来传递sessiontoken,效果是一样的。3.输入错误的Token会导致连接失败!示例地址以上代码已经上传到git,示例地址:码云:sa-token-demo-websocket参考资料Gitee地址:https://gitee.com/dromara/sa-tokenGitHub地址:https://github。com/dromara/sa-tokenSa-Token官网:https://sa-token.dev33.cn/