Аутентификация и авторизация Websocket весной

Я много пытаюсь правильно внедрить аутентификацию и авторизацию Stomp (websocket) с помощью Spring-Security. Для потомков я отвечу на свой вопрос, чтобы предоставить руководство.

Проблема

Документация Spring WebSocket (для аутентификации) выглядит нечетко ATM (IMHO). И я не мог понять, как правильно обрабатывать аутентификацию и авторизацию .

Что я хочу

  • Аутентификация пользователей с использованием логина / пароля.
  • Предотвратите анонимные пользователи CONNECT, хотя WebSocket.
  • Добавьте уровень авторизации (user, admin, …).
  • Наличие Principal доступно в controllerах.

Что я не хочу

  • Аутентификация на конечных точках согласования HTTP (так как большинство библиотек JavaScript не отправляют заголовки аутентификации вместе с переговорным вызовом HTTP).

Как указано выше, документация (ATM) неясна, пока весна не предоставит четкую документацию, вот шаблон, который позволит вам не тратить два дня на то, чтобы понять, что делает цепочка безопасности.

Очень хорошая попытка была сделана Роб-Леггеттом, но он раздвигал class Спрингса, и вам следует избегать этого как можно больше .

Что нужно знать:

  • Цепочка безопасности и Конфигурация безопасности для http и WebSocket полностью независимы.
  • Spring AuthentionProvider вообще не участвует в аутентификации Websocket.
  • После установки запроса CONNECT пользователь ( simpUser ) будет сохранен, и больше запросов на аутентификацию не потребуется.

Maven deps

  org.springframework.boot spring-boot-starter-websocket   org.springframework spring-messaging   org.springframework.boot spring-boot-starter-security   org.springframework.security spring-security-messaging  

Конфигурация WebSocket

В приведенной ниже конфигурации зарегистрируйте простой брокер сообщений (обратите внимание, что он не имеет ничего общего с аутентификацией или авторизацией).

 @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(final MessageBrokerRegistry config) { // These are endpoints the client can subscribes to. config.enableSimpleBroker("/queue/topic"); // Message received with one of those below destinationPrefixes will be automatically router to controllers @MessageMapping config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // Handshake endpoint registry.addEndpoint("stomp"); // If you want to you can chain setAllowedOrigins("*") } } 

Конфигурация безопасности весны

Поскольку протокол Stomp полагается на первый HTTP-запрос, нам нужно будет разрешить HTTP-вызов нашей конечной точке рукопожатия.

 @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception { // This is not for websocket authorization, and this should most likely not be altered. http .httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/stomp").permitAll() .anyRequest().denyAll(); } } 

Затем мы создадим службу, отвечающую за аутентификацию пользователей.

 @Component public class WebSocketAuthenticatorService { // This method MSUT return a UsernamePasswordAuthenticationToken, another component in the security chain is testing it with 'instanceof' public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username, final String password) throws AuthenticationException { if (username == null || username.trim().length()) { throw new AuthenticationCredentialsNotFoundException("Username was null or empty."); } if (password == null || password.trim().length()) { throw new AuthenticationCredentialsNotFoundException("Password was null or empty."); } // Add your own logic for retrieving user in fetchUserFromDb() if (fetchUserFromDb(username, password) == null) { throw new BadCredentialsException("Bad credentials for user " + username); } // null credentials, we do not pass the password along return new UsernamePasswordAuthenticationToken( username, null, Collections.singleton((GrantedAuthority) () -> "USER") // MUST provide at least one role ); } } 

Обратите внимание: UsernamePasswordAuthenticationToken ДОЛЖЕН иметь GrantedAuthorities, если вы используете другой конструктор, Spring будет автоматически задавать isAuthenticated = false .

Почти там, теперь нам нужно создать Interceptor, который установит заголовок simpUser или выбросит AuthenticationException в сообщениях CONNECT.

 @Component public class AuthChannelInterceptorAdapter extends ChannelInterceptor { private static final String USERNAME_HEADER = "login"; private static final String PASSWORD_HEADER = "passcode"; private final WebSocketAuthenticatorService webSocketAuthenticatorService; @Inject public AuthChannelInterceptorAdapter(final WebSocketAuthenticatorService webSocketAuthenticatorService) { this.webSocketAuthenticatorService = webSocketAuthenticatorService; } @Override public Message preSend(final Message message, final MessageChannel channel) throws AuthenticationException { final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT == accessor.getCommand()) { final String username = accessor.getFirstNativeHeader(USERNAME_HEADER); final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER); final UsernamePasswordAuthenticationToken user = webSocketAuthenticatorService.getAuthenticatedOrFail(username, password); accessor.setUser(user); } return message; } } 

Обратите внимание, что: preSend() ДОЛЖНО вернуть preSend() , другой элемент в цепочке безопасности весны проверяет это. Обратите внимание: если ваше UsernamePasswordAuthenticationToken было GrantedAuthority без передачи GrantedAuthority , аутентификация завершится неудачно, потому что конструктор без разрешенных полномочий автоматически установит authenticated = false ЭТО ВАЖНАЯ ДЕТАЛИ, которая не зарегистрирована в весенней безопасности .

Наконец, создайте еще два classа для обработки соответственно авторизации и аутентификации.

 @Configuration @Order(Ordered.HIGHEST_PRECEDENCE + 99) public class WebSocketAuthenticationSecurityConfig extends WebSocketMessageBrokerConfigurer { @Inject private AuthChannelInterceptorAdapter authChannelInterceptorAdapter; @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // Endpoints are already registered on WebSocketConfig, no need to add more. } @Override public void configureClientInboundChannel(final ChannelRegistration registration) { registration.setInterceptors(authChannelInterceptorAdapter); } } 

Обратите внимание: @OrderCRUCIAL , не забудьте его, он позволяет зарегистрировать наш перехватчик сначала в цепочке безопасности.

 @Configuration public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) { // You can customize your authorization mapping here. messages.anyMessage().authenticated(); } // TODO: For test purpose (and simplicity) i disabled CSRF, but you should re-enable this and provide a CRSF endpoint. @Override protected boolean sameOriginDisabled() { return true; } } 

Удачи !

для клиентской стороны java используйте этот пример:

 StompHeaders connectHeaders = new StompHeaders(); connectHeaders.add("login", "test1"); connectHeaders.add("passcode", "test"); stompClient.connect(WS_HOST_PORT, new WebSocketHttpHeaders(), connectHeaders, new MySessionHandler); 
  • Ошибка манифеста слияния
  • Добавление элементов управления яркостью экрана в приложение для Android
  • Как создать кнопку динамически в android?
  • Как изменить fragmentы с помощью Android-навигатора
  • String.replaceAll без RegEx
  • Почему в Java есть перегрузка и переопределение метода?
  • Android: прослушиватель изменений в подключении к Интернету
  • Расширение от двух classов
  • Неверные данные местоположения Android GPS по запросу
  • Выполнение другой java-программы из нашей java-программы
  • SDK Manager.exe не работает
  • Давайте будем гением компьютера.