如何确定 Spring 消息传递中 ChannelInterceptor 的执行顺序?

问题描述 投票:0回答:1

我遇到的问题是 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,配置时可以使用addFilterAtaddFilterBeforeaddFilterAfter方法。

我找不到方法,但也许我错过了一些东西。

java spring websocket interceptor messaging
1个回答
0
投票

过了一段时间,我回到这个问题并做出了一个决定。也许不是最好的,但相当实用。 首先,我们需要使用不带参数的@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 更简单。如果您分享您的想法,我会很高兴。

© www.soinside.com 2019 - 2024. All rights reserved.