2017-01-29 9 views
3

Позвольте мне предисловие, сказав, что для моего сценария будет отказано в доступе . Построен в Spring Boot с Spring Security 4. Я разрешаю любому подключиться к websocket и подписаться на тему, но я гарантирую возможность отправлять сообщения в эту тему со следующей конфигурацией безопасности сетевого сокета:Spring Websocket Security throwing AccessDeniedException

@Configuration 
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { 

    @Override 
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { 
     messages 
       .simpSubscribeDestMatchers("/main-page-feed/thought-queue/**").permitAll() 
       .simpDestMatchers("/thought-bubble/push-to-queue/**").authenticated(); 


    } 

    @Override 
    protected boolean sameOriginDisabled() { 
     return true; 
    } 

} 

Так что, когда вы пытаетесь отправить сообщение /thought-bubble/push-to-queue в то время как неаутентифицированные, он отрицает вас (что правильно, я хочу это подчеркнуть, потому что только другие вопросы, которые я могу найти на это есть, когда исключение неправильно выброшены) и выбрасывает AccessDeniedException. Я не понимаю, когда, в отличие от безопасности websocket, Spring HTTP-защита блокирует что-то и отказывается от доступа, она не генерирует исключение, она просто отправляет статус HTTP. Я пробовал использовать @ExceptionHandler, AccessDenied обработчики, но ничего, что я пробовал, удалось поймать и обработать это исключение. Ниже представлена ​​трассировка стека и другие соответствующие файлы, любые идеи оцениваются, потому что я довольно застрял. Я пробовал переходить через исходный код в debug, но я действительно не вижу, в чем проблема.

Трассировка стека:

org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied 
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:127) ~[spring-messaging-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:104) ~[spring-messaging-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:298) ~[spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:307) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.delegateMessages(AbstractSockJsSession.java:382) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession.handleMessage(WebSocketServerSockJsSession.java:193) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler.handleTextMessage(SockJsWebSocketHandler.java:92) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:110) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:42) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:78) [spring-websocket-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:399) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:106) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:500) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:295) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:131) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:69) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-8.5.6.jar:8.5.6] 
    at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_101] 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_101] 
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.6.jar:8.5.6] 
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_101] 
    Caused by: org.springframework.security.access.AccessDeniedException: Access is denied 
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE] 
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.1.4.RELEASE.jar:4.1.4.RELEASE] 
    at org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor.preSend(ChannelSecurityInterceptor.java:71) ~[spring-security-messaging-4.0.2.RELEASE.jar:4.0.2.RELEASE] 
    at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:158) ~[spring-messaging-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:113) ~[spring-messaging-4.3.5.RELEASE.jar:4.3.5.RELEASE] 
    ... 30 common frames omitted 

Security Config:

@Configuration 
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter { 

    @Autowired 
    private UserProfileService userProfileService; 

    @Autowired 
    private CustomAccessDeniedHandler accessDeniedHandler; 

    @Autowired 
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { 
     auth.userDetailsService(userProfileService); 
     auth.authenticationProvider(authenticationProvider()); 
    } 

    @Bean 
    public PasswordEncoder passwordEncoder() { 
     return new BCryptPasswordEncoder(); 
    } 

    @Bean 
    public DaoAuthenticationProvider authenticationProvider() { 
     DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); 
     authenticationProvider.setUserDetailsService(userProfileService); 
     authenticationProvider.setPasswordEncoder(passwordEncoder()); 
     return authenticationProvider; 
    } 

    @Override 
    public void configure(WebSecurity web) throws Exception { 
     web.ignoring().antMatchers("/css/**", "/javascript/**"); 
    } 

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http .httpBasic() 
        .and() 
       .authorizeRequests() 
        .antMatchers("/", "/index.html", "/home.html", "/getLatestPost", "/application-socket-conn/**").permitAll() 
        .anyRequest().authenticated() 
        .and() 
       .exceptionHandling().accessDeniedHandler(accessDeniedHandler).and() 
       .csrf().disable(); 
    } 
} 

Web Оправа Config:

@Configuration 
@EnableWebSocketMessageBroker 
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{ 

    @Override 
    public void configureMessageBroker(MessageBrokerRegistry config) { 
     //Javascript connection subscribes to this URI 
     config.enableSimpleBroker("/main-page-feed"); 

     //STOMP messages are sent to this URI + suffix 
     config.setApplicationDestinationPrefixes("/thought-bubble"); 
    } 

    @Override 
    public void registerStompEndpoints(StompEndpointRegistry registry) { 
     //URI used for SockJS connection 
     registry.addEndpoint("/application-socket-conn").withSockJS(); 
    } 
} 

Пользовательские AccessDenied Handler (не ловя):

@Component 
public class CustomAccessDeniedHandler implements AccessDeniedHandler { 

    @Autowired 
    @Qualifier("clientOutboundChannel") 
    private MessageChannel clientOutboundChannel; 

    @Override 
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { 
     Message<String> message = new Message<String>() { 
      @Override 
      public String getPayload() { 
       return "Access denied."; 
      } 

      @Override 
      public MessageHeaders getHeaders() { 
       return null; 
      } 
     }; 

     clientOutboundChannel.send(message); 
    } 
} 

Web Оправа контроллер

@RestController 
public class WebSocketController { 

    @Autowired 
    private SimpMessagingTemplate simpMessagingTemplate; 

    @MessageMapping("/push-to-queue") 
    public void pushThoughtToQueue(@Payload ThoughtEntity entity) throws Exception { 
     Date today = new Date(Calendar.getInstance().getTimeInMillis()); 

     entity.setPostDate(today); 
     entity.setFavoriteCount(0); 

     this.simpMessagingTemplate.convertAndSend("/main-page-feed/thought-queue", entity); 
    } 
} 

Я думаю AffirmativeBased класс, где это становится выброшено, но я не знаю, почему, или почему нормальная (не веб-розетка) пружина безопасность не делает этого, когда он лишает доступ к чему-то. Как я уже сказал, это правильно лишает доступ, но он бросает исключение во время выполнения и отправляет уродливую стеклу обратно клиенту через websocket.

UPDATE:

Я понял, что исключение или не авторизован пользователь, поэтому я думаю, что это другой вопрос. Как я уже сказал, я не использую ROLE s для этого приложения, и я думаю, что это как-то связано с этим. В настоящее время я изучаю анонимные роли, потому что я думаю, что это связано с тем, что происходит.

+0

Ну я попробовал почти все мыслимые и я не могу понять это так, поэтому любой ввод или идеи (независимо от того, как это диковинно) приветствуются на этом этапе. –

ответ

1

кажется, что ваш код клиента websocket не отправляет информацию авторизации. Обычно по умолчанию он не обрабатывается, и вы должны создать свой собственный способ подключения с безопасностью.

В моем случае я использую авторизацию oauth и должен указать конкретный заголовок Authorization : Bearer _uuid_token_, который указан во время соединения stompClient.

Посмотрите на этот фрагмент, чтобы получить общее представление.(Я использую AngularJS)

(function() { 
    'use strict'; 
    /* globals SockJS, Stomp */ 

    angular 
     .module('myApp') 
     .factory('global_WebSocket', GlobalWebSocketClient); 

    GlobalWebSocketClient.$inject = ['$window', 'localStorageService', '$q']; 

    function GlobalWebSocketClient($window, localStorageService, $q) { 

     var connected = $q.defer(); 

     var established = {established: false} ; 

     var loc = $window.location; 
     var url = loc.protocol + '//' + loc.host + loc.pathname + 'websocket'; 
     var token = localStorageService.get('token'); 
     if (token && token.expires_at && token.expires_at > new Date().getTime()) { 
      url += '?access_token=' + token.access_token; 
     } else { 
      url += '?access_token=no token'; 
     } 

     /*jshint camelcase: false */ 
     var socket = new SockJS(url); 


     /*jshint camelcase: false */ 
     var stompClient = Stomp.over(socket); 
     var headers = { 
      Authorization : 'Bearer ' + token.access_token, 
     }; 

     stompClient.debug = null; 


     var establishConnection = function() { 
      stompClient.connect(headers, function() { 
       established.established = true; 
       connected.resolve('success'); 
      }, function(error) { 
       console.log("ERROR CONNECTNG!"); 
       console.log(error.headers); 
       establishConnection(); 
      }); 
     }; 


     establishConnection(); 



     return { 
      connected: connected, 
      client: stompClient, 
      established: established 
     }; 
    } 
})(); 

Как вы можете видеть, этот код создает URL добавления access_token, а также определяет заголовки авторизации, которые были созданы ранее и были сохранены в localStorageService. Я предполагаю, что вы предполагали, что ваш клиент отправляет заголовки по умолчанию, но с sockjs это не так.

Тогда я могу создать такой клиент-сервис

(function() { 
    'use strict'; 
    /* globals SockJS, Stomp */ 

    angular 
     .module('myApp') 
     .factory('synchronization_Status', SynchronizationFileTrackerService); 

    SynchronizationFileTrackerService.$inject = ['global_WebSocket']; 

    function SynchronizationFileTrackerService (global_WebSocket) { 
     var stompClient = global_WebSocket; 
     var subscriber = {} ; 


     return { 
      subscribe: subscribe, 
      unsubscribe: unsubscribe 
     }; 

     function unsubscribe(target) { 
      if (subscriber[target]) { 
       subscriber[target].unsubscribe(); 
      } 
     }; 


     function subscribe(forTarget, handler) { 
       if (stompClient.established.established) { 
        subscriber[forTarget] = stompClient.client.subscribe('/synchronization/status/' + forTarget, function(data) { 
         data = angular.fromJson(data.body); 
         handler(angular.fromJson(data)); 

        }); 
       } else { 
        stompClient.connected.promise.then(
         function() { 
          subscriber[forTarget] = stompClient.client.subscribe('/synchronization/status/' + forTarget, function(data) { 
           data = angular.fromJson(data.body); 
           handler(angular.fromJson(data)); 

          }); 
         }, null, null); 
       } 
     } 
    } 
})(); 

И повторно использовать такую ​​услугу в моем коде UI просто:

synchronization_Status.subscribe(ctrl.id, funciton(response){ctrl.currentStatus = response}); 
+0

Хорошо, поэтому заголовки решили половину моей проблемы. Я также использую Angular, и довольно просто было просто сохранить заголовки 'Basic' в' $ rootScope', а затем использовать это для моего рукопожатия. Теперь аутентифицированный пользователь может отправить сообщение без «AccessDeniedException», что является правильным. Тем не менее, пользователь, не прошедший проверку подлинности, все еще получает уродливое «AccessDeniedException», которое не отображается в стеке, а затем отправляет его обратно через websocket. Так что это по крайней мере сейчас, но мне все же нужно выяснить исключение. –

+0

Ну AccessDeniedHandler, как указано в документах, используется только в ExceptionTranslationFilter. Эти фильтры не применяются в обработке WebSocket. Вы должны создавать свои собственные перехватчики и перерабатывать доставку сообщений. –

+0

Хорошо, что имеет смысл. Я просматривал оба документа, но я не мог понять, где некоторые из разъединений были между Spring Security и обработкой Websocket. Реализация перехватчика в этом вопросе выглядит примерно так, как мне нужно: http://stackoverflow.com/questions/21554230/how-to-reject-topic-subscription-based-on-user-rights-with-spring-websocket –