代码之家  ›  专栏  ›  技术社区  ›  We are Borg

Spring WebSockets:Spring安全授权不在WebSockets内工作

  •  0
  • We are Borg  · 技术社区  · 6 年前

    我正在开发一个SpringMVC应用程序,在该应用程序中,我们有用于身份验证和授权的SpringSecurity。我们正在致力于迁移到SpringWebSockets,但在WebSocket连接中获取经过身份验证的用户时遇到了问题。安全上下文在WebSocket连接中根本不存在,但在常规HTTP中工作正常。我们做错了什么?

    WebSocketConfig:

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/topic");
            config.setApplicationDestinationPrefixes("/app");
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/app").withSockJS();
        }
    }
    

    在下面的控制器中,我们正在尝试获取当前经过身份验证的用户,该用户始终为空。

    @Controller
    public class OnlineStatusController extends MasterController{
    
        @MessageMapping("/onlinestatus")
        public void onlineStatus(String status) {
            Person user = this.personService.getCurrentlyAuthenticatedUser();
            if(user!=null){
                this.chatService.setOnlineStatus(status, user.getId());
            }
        }
    }
    

    security-applicationcontext.xml:

      <security:http pattern="/resources/**" security="none"/>
        <security:http pattern="/org/**" security="none"/>
        <security:http pattern="/jquery/**" security="none"/>
        <security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
            <security:form-login login-page="/login" username-parameter="j_username" password-parameter="j_password"
                                 login-processing-url="/j_spring_security_check" default-target-url="/canvaslisting"
                                 always-use-default-target="false" authentication-failure-url="/login?error=auth"/>
            <security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
                                  token-validity-seconds="1209600" data-source-ref="dataSource"/>
            <security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
            <security:csrf disabled="true"/>
            <security:intercept-url pattern="/cometd/**" access="permitAll" />
            <security:intercept-url pattern="/app/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')" />
    <!--        <security:intercept-url pattern="/**" requires-channel="https"/>-->
            <security:port-mappings>
                <security:port-mapping http="80" https="443"/>
            </security:port-mappings>
            <security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>
            <security:session-management session-fixation-protection="newSession">
                <security:concurrency-control session-registry-ref="sessionReg" max-sessions="5" expired-url="/login"/>
            </security:session-management>
        </security:http>
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   AlgorithmFromHell    6 年前

    我记得在我正在做的一个项目中,遇到了同样的问题。由于我无法使用Spring文档找到解决方案——以及其他有关堆栈溢出的答案对我不起作用——所以我最终创建了一个解决方案。

    技巧本质上是强制应用程序在WebSocket连接请求上对用户进行身份验证。要做到这一点,您需要一个拦截此类事件的类,然后一旦您控制了该类,就可以调用您的身份验证逻辑。

    创建实现Spring的类 ChannelInterceptorAdapter . 在这个类中,可以注入执行实际身份验证所需的任何bean。我的示例使用基本身份验证:

    @Component
    public class WebSocketAuthInterceptorAdapter extends ChannelInterceptorAdapter {
    
    @Autowired
    private DaoAuthenticationProvider userAuthenticationProvider;
    
    @Override
    public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
    
        final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        StompCommand cmd = accessor.getCommand();
    
        if (StompCommand.CONNECT == cmd || StompCommand.SEND == cmd) {
            Authentication authenticatedUser = null;
            String authorization = accessor.getFirstNativeHeader("Authorization:);
            String credentialsToDecode = authorization.split("\\s")[1];
            String credentialsDecoded = StringUtils.newStringUtf8(Base64.decodeBase64(credentialsToDecode));
            String[] credentialsDecodedSplit = credentialsDecoded.split(":");
            final String username = credentialsDecodedSplit[0];
            final String password = credentialsDecodedSplit[1];
            authenticatedUser = userAuthenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(username, password));
            if (authenticatedUser == null) {
                throw new AccessDeniedException();
            } 
            SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
            accessor.setUser(authenticatedUser);    
     }
        return message;
     }
    

    然后,在你的 WebSocketConfig 类,您需要注册拦截器。将上述类作为bean添加并注册。在这些更改之后,您的类将如下所示:

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    
    @Autowired
    private WebSocketAuthInterceptorAdapter authInterceptorAdapter;
    
    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/app").withSockJS();
    }
    
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(authInterceptorAdapter);
        super.configureClientInboundChannel(registration);
    }
    }
    

    显然,身份验证逻辑的细节由您决定。您可以调用JWT服务或您正在使用的任何服务。