我正在使用 spring boot angular 创建一个演示聊天应用程序。我已经使用 Spring Security 实现了 JWT 身份验证。我能够成功进行身份验证,但无法建立成功的 WebSocket 连接。它失败并出现以下错误:
stomp.js:134 - Opening Web Socket...
zone-evergreen.js:2863 - GET http://localhost:8090/chatal/info?t=1617996229738 40
stomp.js:134 Whoops! Lost connection to http://localhost:8090/chatal
这很奇怪,因为在 WebSocket 配置中我启用了允许来源。
registry.addEndpoint("/chatal").setAllowedOrigins("*").withSockJS();
以下是后端的配置:
- 使用 jwt 的应用程序安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 在 WEB MVC 配置中启用 CORS
@EnableWebMvc
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600L)
.allowedHeaders("*")
.exposedHeaders("Authorization","X-Auth-Token")
.allowCredentials(true);
}
}
- WebSocket 配置
@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@Slf4j
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chatal").setAllowedOrigins("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
- WebSocket 安全配置
@Configuration
public class WebSocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.anyMessage().authenticated();
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
- 负责与后端连接的 Angular 聊天服务:
import {Injectable, OnDestroy} from '@angular/core';
import {Client, Message, over} from "stompjs";
import {BehaviorSubject, Observable} from "rxjs";
import {SocketState} from "../model/SocketState";
import {environment} from "../../environments/environment";
import * as SockJS from "sockjs-client";
import {filter, first, switchMap} from "rxjs/operators";
import {StompSubscription} from "@stomp/stompjs";
import {LocalStorageService} from "./local-storage.service";
import {AppSettings} from "../app.settings";
@Injectable({
providedIn: 'root'
})
export class ChatService implements OnDestroy {
private client: Client;
private state: BehaviorSubject<SocketState>;
constructor(private _localStorageSvc:LocalStorageService) {
let token = this._localStorageSvc.get(AppSettings.AUTH_RESPONSE,{}).authenticationToken;
const customHeaders ={
"Authorization": `${token}`
};
console.log(customHeaders);
this.client = over(<WebSocket>new SockJS(environment.socketURL));
this.state = new BehaviorSubject<SocketState>(SocketState.ATTEMPTING);
this.client.connect(customHeaders, () => {
this.state.next(SocketState.CONNECTED);
});
}
private connect(): Observable<Client> {
return new Observable<Client>(observer => {
this.state.pipe(filter(state => state === SocketState.CONNECTED)).subscribe(() => {
observer.next(this.client);
})
});
}
onMessage(topic: string, handler = ChatService.jsonHandler): Observable<any> {
return this.connect().pipe(first(), switchMap(client => {
return new Observable<any>(observer => {
const subscription: StompSubscription = client.subscribe(topic, message => {
observer.next(handler(message));
});
return () => client.unsubscribe(subscription.id);
});
}));
}
onPlainMessage(topic: string): Observable<string> {
return this.onMessage(topic, ChatService.textHandler);
}
send(topic: string, payload: any): void {
this.connect()
.pipe<Client>(first())
.subscribe(client => client.send(topic, {}, JSON.stringify(payload)));
}
static jsonHandler(message: Message): any {
return JSON.parse(message.body);
}
static textHandler(message: Message): string {
return message.body;
}
ngOnDestroy() {
this.connect().pipe<Client>(first()).subscribe(client=> client.disconnect(null as any));
}
}
- 认证后,用户重定向了聊天组件:
export class ChatRoomComponent implements OnInit {
constructor(private _chatService:ChatService) { }
ngOnInit(): void {
}
}
我已经尝试使用邮递员使用以下 URI:
- 网址:http://localhost:8090/chatal
- 方法:获取
- 授权:不记名令牌...
- 结果:欢迎使用 SockJS!
- 网址:http://localhost:8090/chatal/info?t=1617904524809
- 方法:获取
- 授权:不记名令牌...
- 结果: {"entropy":1335940544,"origins":[" : "],"cookie_needed":true,"websocket":true}
此解决方案不起作用:
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chatal")
.setAllowedOriginPatterns("*")
.withSockJS();
}
该方法setAllowedOriginPatterns
不存在,如下所示: