SpringBoot项目整合WebSocket

在项目中经常需要在后端启动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禁用,因为不使用session
.csrf().disable()
// 禁用HTTP响应标头
.headers().cacheControl().disable().and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 将上面的websocket的地址添加允许匿名访问
.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; # 默认60秒无消息自动超时断开,改成600秒,需要搭配心跳检测重置
}

配置云厂商的防火墙(WAF)配置

一般云厂商都会提供一些防火墙产品,其中就包括对websockt的开关功能,如果此开关关闭,会导致生产环境无法建立websocket连接

特征判断

在Ningx配置正确的前提下,使用抓包工具对请求进行抓包,如果请求头中缺少Upgrade: websocketConnection: upgrade,那么大概率是云厂商的防火墙产品进行了拦截处理,需要联系运维部门确认是否接入了云厂商的防火墙服务

配置参考

腾讯云配置


SpringBoot项目整合WebSocket
http://blog.jingxiang.ltd/2024/10/11/SpringBoot项目整合WebSocket/
作者
yemangran
发布于
2024年10月11日
许可协议