在项目中经常需要在后端启动websocket服务进行双向通信,故在此记录一下业务中的整合过程
配置WebSocket端点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowried private WebSocketInterceptor webSocketInterceptor;
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry .addHandler(myHandler(), "/myHandler") .addInterceptors(webSocketInterceptor); }
@Bean public WebSocketHandler myHandler() { return new MyHandler(); }
}
|
上面的示例代码,使用@Configuration
标注了一个SpringBoot的配置类,同时使用@EnableWebSocket
启用WebSocket的功能,然后实现WebSocketConfigurer
并重写registerWebSocketHandlers
方法,将指定路径分配给一个拥有具体业务逻辑的WebSocketHandler
进行处理
配置WebSocketInterceptor拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class WebSocketInterceptor implements HandshakeInterceptor {
@Autowired private TokenService tokenService;
@Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out.println("ws开始握手"); List<String> tokens = request.getHeaders().get("Authorization"); if (CollectionUtils.isEmpty(tokens)) { return false; } String token = tokens.get(0); LoginUser loginUser = tokenService.getLoginUser(token); if (loginUser != null) { attributes.put("id",loginUser.getUserId()); attributes.put("uid", loginUser.getUser().getUserName()); response.getHeaders().put("Sec-WebSocket-Protocol", tokens); return true; } else { return false; } } }
|
上面代码的作用就是在WebSocket进行握手连接时,读取请求头中的Authorization
的内容获取用户的token进行身份认证
配置SpringSecurity放行WebSocket的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf().disable() .headers().cacheControl().disable().and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers("/login", "/register", "/captchaImage", "/app/login/auto", "/public/reset/**","/myHandler/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable(); } }
|
在上面的代码中将/myHandler
添加到可以匿名访问的地址中去,如果不添加则无法建立socket连接,用户身份认证则由WebSocketInterceptor
进行处理
配置Nginx的WebSocket连接
普通的常规的前后端分离非微服务项目,需要专门为WebSocket配置Nginx
1 2 3 4 5 6 7 8 9 10
| location /ws/ { proxy_pass http://localhost:8036/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600; }
|
配置云厂商的防火墙(WAF)配置
一般云厂商都会提供一些防火墙产品,其中就包括对websockt的开关功能,如果此开关关闭,会导致生产环境无法建立websocket连接
特征判断
在Ningx配置正确的前提下,使用抓包工具对请求进行抓包,如果请求头中缺少Upgrade: websocket
或Connection: upgrade
,那么大概率是云厂商的防火墙产品进行了拦截处理,需要联系运维部门确认是否接入了云厂商的防火墙服务
配置参考
