Java Spring WebSocket:Spring安全授权在WebSocket内不起作用

Java Spring WebSocket:Spring安全授权在WebSocket内不起作用,java,spring,spring-security,websocket,spring-websocket,Java,Spring,Spring Security,Websocket,Spring Websocket,我正在开发一个SpringMVC应用程序,在该应用程序中,我们使用SpringSecurity进行身份验证和授权。我们正在致力于迁移到SpringWebSocket,但是在websocket连接中获取经过身份验证的用户时遇到了问题。安全上下文在websocket连接中根本不存在,但可以与常规HTTP配合使用。我们做错了什么 WebsocketConfig: @Configuration @EnableWebSocketMessageBroker public class WebSocketCon

我正在开发一个SpringMVC应用程序,在该应用程序中,我们使用SpringSecurity进行身份验证和授权。我们正在致力于迁移到SpringWebSocket,但是在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>

我记得我在一个项目中遇到了同样的问题。由于我无法使用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;
 }

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

我记得在我从事的一个项目中遇到了同样的问题。由于我无法使用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;
 }

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

如果您使用SockJS+Stomp并正确配置了安全性,您应该能够通过常规用户名/pw身份验证器(如@AlgorithmFromHell and do)进行连接

accessor.setUser(authentication.getPrincipal())//stomp头访问器

如果您使用的是SockJS+Stomp并正确配置了安全性,您应该能够通过常规用户名/pw验证器连接,如@AlgorithmFromHell and do

accessor.setUser(authentication.getPrincipal())//stomp头访问器

您能告诉我WebSocketChannelInterceptorAdapter来自哪个类吗?谢谢。该类应该是
WebSocketAuthInterceptorAdapter
。我编辑了我的答案。我实现了类似的代码,我可以在拦截器中获取用户,但在使用
SecurityContextHolder.getContext()时处于服务状态,我得到错误:
在SecurityContext中找不到身份验证对象
很抱歉这么晚才响应。我已经有一段时间没有做任何与SpringWebSocket相关的事情了。我假设这个错误一定意味着在Spring Security中没有正确配置某些东西,并且框架没有解释您正在设置的授权。您能告诉我WebSocketChannelInterceptorAdapter来自哪个类吗?谢谢。该类应该是
WebSocketAuthInterceptorAdapter
。我编辑了我的答案。我实现了类似的代码,我可以在拦截器中获取用户,但在使用
SecurityContextHolder.getContext()时处于服务状态,我得到错误:
在SecurityContext中找不到身份验证对象
很抱歉这么晚才响应。我已经有一段时间没有做任何与SpringWebSocket相关的事情了。我假设这个错误一定意味着Spring安全性中没有正确配置某些东西,并且框架没有解释您正在设置的授权。