我遇到的问题是 ChannelInterceptors 的执行顺序。我尝试设置 websocket 安全性。定义了一个ChannelInterseptor来实现基于jwt令牌的身份验证,并添加了它的ChannelRegistration。还添加了 @EnableWebsocketSecurity 注释来启用 websocket 的安全性。同时,添加了定义安全规则的拦截器链。这是我实现WebSocketMessageBrokerConfigurer的类。我的 ChannelInterceptor 添加时间晚于安全拦截器链,导致授权错误。
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final JwtDecoder jwtDecoder;
public WebSocketConfig(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
@Bean
public ChannelInterceptor csrfChannelInterceptor(){
return new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
return ChannelInterceptor.super.preSend(message, channel);
}
};
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
List<String> authorization = accessor.getNativeHeader("Authorization");
String accessToken = authorization.get(0).split(" ")[1];
Jwt jwt = jwtDecoder.decode(accessToken);
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
Authentication authentication = converter.convert(jwt);
accessor.setUser(authentication);
}
return message;
}
});
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
@Bean
public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.anyMessage().authenticated();
return messages.build();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
.setAllowedOrigins("http://localhost:8080", "http://localhost:4200");
registry.addEndpoint("/chat")
.setAllowedOrigins("http://localhost:8080", "http://localhost:4200")
.withSockJS();
}
}
问题是 ChannelInterseptors 的注册顺序仅取决于插入顺序,因为一切都是通过常规 ArrayList 实现的。有一种方法 - 确定实现 WebsocketMessageBrokerConfigurer 接口的配置顺序。它们是通过 DelegatingWebSocketMessageBrokerConfiguration 中的 setter 注入的。并且可以使用 @Order 注释来更改集合的实现顺序,将其放置在我们的配置类之上。 这解决了我的问题,但我想知道是否需要明确定义拦截器的顺序,例如嵌入安全拦截器链。可以类比SecurityFilterChain,配置时可以使用addFilterAt、addFilterBefore、addFilterAfter方法。
我找不到方法,但也许我错过了一些东西。
过了一段时间,我回到这个问题并做出了一个决定。也许不是最好的,但相当实用。 首先,我们需要使用不带参数的@Order注解来标记我们的配置,默认情况下它是Integer.MAX_VALUE,这意味着我们的配置将被最后处理,这意味着所有其他配置都已经被处理处理并添加所有拦截器,这允许我们手动执行命令。 这是我的实现方式:
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Order
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final JwtDecoder jwtDecoder;
public WebSocketConfig(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
ChannelInterceptor securityInterceptor = new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
List<String> authorization = accessor.getNativeHeader("Authorization");
String accessToken = authorization.get(0).split(" ")[1];
Jwt jwt = jwtDecoder.decode(accessToken);
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
Authentication authentication = converter.convert(jwt);
accessor.setUser(authentication);
}
return message;
}
@Override
public String toString(){
return "SecurityInterceptor";
}
};
ChannelInterceptor csrf = new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
return ChannelInterceptor.super.preSend(message, channel);
}
@Override
public String toString(){
return "csrfChannelInterceptor";
}
};
addInterceptorBefore(registration, securityInterceptor, AuthorizationChannelInterceptor.class);
addFilterAt(registration, csrf, XorCsrfChannelInterceptor.class);
}
private void addInterceptorBefore(ChannelRegistration registration, ChannelInterceptor interceptor, Class<? extends ChannelInterceptor> before) {
List<ChannelInterceptor> interceptors = getChannelInterceptors(registration);
int index = findInterceptor(interceptors, before);
interceptors.add(interceptors.get(interceptors.size()-1));
for(int i = interceptors.size()-2; i > index; i--){
interceptors.set(i, interceptors.get(i-1));
}
interceptors.set(index, interceptor);
}
private void addFilterAfter(ChannelRegistration registration, ChannelInterceptor interceptor, Class<? extends ChannelInterceptor> after) {
List<ChannelInterceptor> interceptors = getChannelInterceptors(registration);
int index = findInterceptor(interceptors, after);
interceptors.add(interceptors.get(interceptors.size()-1));
for(int i = interceptors.size()-2; i > index + 1; i--){
interceptors.set(i, interceptors.get(i-1));
}
interceptors.set(index+1, interceptor);
}
private void addFilterAt(ChannelRegistration registration, ChannelInterceptor interceptor, Class<? extends ChannelInterceptor> at) {
List<ChannelInterceptor> interceptors = getChannelInterceptors(registration);
int index = findInterceptor(interceptors, at);
interceptors.set(index, interceptor);
}
private List<ChannelInterceptor> getChannelInterceptors(ChannelRegistration registration) {
Field field = ReflectionUtils.findField(registration.getClass(), "interceptors");
Objects.requireNonNull(field);
field.setAccessible(true);
List<ChannelInterceptor> interceptors =
(List<ChannelInterceptor>) ReflectionUtils.getField(field, registration);
Objects.requireNonNull(interceptors);
return interceptors;
}
private int findInterceptor(List<ChannelInterceptor> interceptors, Class<? extends ChannelInterceptor> interceptor) {
int index = -1;
for(int i = 0; i < interceptors.size();i++){
if(interceptors.get(i).getClass().equals(interceptor)){
index = i;
break;
}
}
return index;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
@Bean
public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.anyMessage().authenticated();
return messages.build();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
.setAllowedOrigins("http://localhost:8080", "http://localhost:4200");
registry.addEndpoint("/chat")
.setAllowedOrigins("http://localhost:8080", "http://localhost:4200")
.withSockJS();
}
}
这个实现可能远非理想,但它比实现一些 BeanFactoryPostProcessor 更简单。如果您分享您的想法,我会很高兴。