2017-01-29 91 views
3

讓我先說這個,說我的場景中訪問應該被拒絕。使用Spring Security 4構建Spring Boot。我允許任何人連接到WebSocket並訂閱主題,但我確保能夠使用以下Web套接字安全配置向主題發送消息:彈出Websocket安全性拋出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處理程序,但沒有試過我能夠捕捉並處理這個異常。以下是堆棧跟蹤和其他相關文件,任何想法都會被讚賞,因爲我很困難。我已經嘗試在調試中逐步完成源代碼,但我並沒有真正看到問題所在。

堆棧跟蹤:

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 

安全配置:

@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(); 
    } 
} 

網絡插座配置:

@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(); 
    } 
} 

自定義存取遭拒處理器(未捕獲它):

@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就是這是越來越拋出的類,但我不知道爲什麼,爲什麼還是正常(非web套接字)Spring Security沒有做到這一點時,它拒絕訪問某些東西。就像我說的那樣,它正確地拒絕訪問,但它拋出了運行時異常,並通過websocket將醜陋的堆棧跟蹤發送回客戶端。

UPDATE:

我已經意識到發生異常時,用戶是否登錄,所以我想這是一個不同的問題。就像我說的,我沒有使用ROLE這個應用程序,我認爲這與它有關。我目前正在研究匿名角色,因爲我認爲這涉及到正在發生的事情。

+0

嗯,我已經試過剛纔的一切想象,我想不通這一點,所以任何意見或建議(不管多麼古怪)在這一點上都是受歡迎的。 –

回答

1

看來你的websocket客戶端的代碼沒有發送授權信息。通常它不是默認處理的,你應該創建自己的方式來連接安全。

在我的情況下,我使用oauth授權,並且必須指定在stompClient連接期間指定的特定報頭Authorization : Bearer _uuid_token_

看看這個片段得到一般想法。(我用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' auth頭文件存儲在'$ rootScope'中,然後將其用於握手。現在,經過身份驗證的用戶可以發送消息而不會出現「AccessDeniedException」,這是正確的。但是,未經身份驗證的用戶仍然會遇到StackTrace中未捕獲到的醜陋'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 –

相關問題