индивидуальная защита на пружинном соединении - PullRequest
0 голосов
/ 19 сентября 2019

РЕДАКТИРОВАТЬ

Я нашел очень похожий вопрос Аутентификация и авторизация Websocket весной , который не дал рабочего решения.

ORIGINAL

Я экспериментирую с Spring WebSockets и Security.

Я пытаюсь реализовать пользовательскую процедуру аутентификации в ChannelInterceptor в поисках сообщения CONNECT.Это сообщение имеет несколько пользовательских заголовков для пользователя / пароля.После проверки я звоню SecurityContextHolder.getContext().setAuthentication(..).

Примечание: я использую аутентификацию на основе заголовков на основе токенов в точках доступа.К сожалению, невозможно отправить пользовательский заголовок из браузера на рукопожатие websocket .Мне пришлось настроить конечную точку Websocket так, чтобы .antMatchers(WebSocketConfig.ENDPOINT).permitAll() был доступен каждому.Spring Security

Идея состоит в том, чтобы аутентифицировать пользователя в сообщении о соединении, установить объект авторизации и позволить Spring Security сделать свое волшебство.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    public static final String ENDPOINT = "/gs-guide-websocket";

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(ENDPOINT).setAllowedOrigins("*");
    }

    @Override
    public void configureClientInboundChannel(final ChannelRegistration registration) {
        registration.interceptors(new XAuthChannelInterceptor(authenticationManager));
    }

    public static class XAuthChannelInterceptor implements ChannelInterceptor {

        private AuthenticationManager authenticationManager;

        public XAuthChannelInterceptor(AuthenticationManager authenticationManager) {
            this.authenticationManager = authenticationManager;
        }

        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {

            var accessor = SimpMessageHeaderAccessor.wrap(message);

            // Check Connect
            SimpMessageType messageType = accessor.getMessageType();
            if (messageType == SimpMessageType.CONNECT) {
                try {
                    // Login Credentials
                    var authUser = accessor.getFirstNativeHeader(CustomAuthenticationFilter.X_HEADER_USER);
                    var authPassword = accessor.getFirstNativeHeader(CustomAuthenticationFilter.X_HEADER_PASS);
                    // Try Login
                    var requestAuthentication = new UsernamePasswordAuthenticationToken(authUser, authPassword);
                    var responseAuthentication = authenticationManager.authenticate(requestAuthentication);
                    // Set User
                    accessor.setUser(responseAuthentication);
                    SecurityContextHolder.getContext().setAuthentication(responseAuthentication);
                } catch (Exception e) {
                    throw new RuntimeException("login failed");
                }
            }

            return message;

        }
    }

}

Кажется, это работает просто отлично.Но: когда сообщение поступает в контроллер, объект аутентификации исчезает.Ну, это потому, что SecurityContextHolder работает ThreadLocal.

@Controller
public class GreetingController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public CreateCustomerCommandResult greeting(CreateCustomerCommand cmd) throws Exception {

        var x = SecurityContextHolder.getContext().getAuthentication();

        Thread.sleep(1000); // simulated delay
        var ret = new CreateCustomerCommandResult();
        ret.setId(UUID.randomUUID());
        ret.setText("Hello, " + cmd.getName());
        return ret;
    }
}

В конечном итоге я хотел бы заставить @PreAuthorize работать так:

@MessageMapping("/hello")
@SendTo("/topic/greetings")
@PreAuthorize("hasAuthority('ADMIN')")
public CreateCustomerCommandResult greeting(CreateCustomerCommand cmd) throws Exception { 
    //...
}

Мой вопрос: как мне установитьобъект аутентификации внутри ChannelInterceptor для SecurityContext контроллера?

...