Spring中的Websocket身份验证和授权 [英] Websocket Authentication and Authorization in Spring

查看:4422
本文介绍了Spring中的Websocket身份验证和授权的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在努力用Spring-Security正确实现Stomp(websocket)身份验证授权对于后代,我会回答我自己的问题以提供指南。

I've been struggling a lot to properly implement Stomp (websocket) Authentication and Authorization with Spring-Security. For posterity i'll answer my own question to provide a guide.


Spring WebSocket文档(用于身份验证)看起来不太清楚ATM(恕我直言)。我无法理解如何正确处理身份验证授权

Spring WebSocket documentation (for Authentication) looks unclear ATM (IMHO). And i couldn't understand how to properly handle Authentication and Authorization.



  • 使用登录名/密码验证用户。

  • 防止匿名用户通过WebSocket进行连接。

  • 添加授权层(用户,管理员,...)。

  • 拥有 Principal 在控制器中可用。

  • Authenticate users with login/password.
  • Prevent anonymous users to CONNECT though WebSocket.
  • Add authorization layer (user, admin, ...).
  • Having Principal available in controllers.



  • 在HTTP协商端点上进行身份验证(因为大多数JavaScript库都没有' t发送身份验证标头以及HTTP协商调用)。

推荐答案

如上所述文档(ATM)目前还不清楚,直到Spring提供一些明确的文档,这里有一个样板,可以帮助你节省两天时间,试图了解安全链正在做什么。

As stated above the documentation (ATM) is unclear, until Spring provide some clear documentation, here is a boilerplate to save you from spending two days trying to understand what the security chain is doing.

A真实很好的尝试是由 Rob-Leggett 做出的,但他是分叉一些Springs类我觉得这样做不舒服。

A really nice attempt was made by Rob-Leggett but, he was forking some Springs class and i don't feel comfortable doing that.

要了解的事项:


  • http和WebSocket的安全链安全配置是完全独立的。

  • Spring AuthenticationProvider 在Websocket身份验证中完全不参与。

  • 一旦在CONNECT请求上设置,用户 simpUser )将被存储,并且不再需要对其他消息进行身份验证(这是websocket的好处之一)。

  • Security chain and Security config for http and WebSocket are completely independent.
  • Spring AuthenticationProvider take not part at all in Websocket authentication.
  • Once set on CONNECT request, the user (simpUser) will be stored and no more authentication will be required on further messages (that's one of the benefits of websocket).
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-messaging</artifactId>
</dependency>



WebSocket配置



以下配置寄存器一个简单的消息代理(注意它与认证和授权无关)。

WebSocket configuration

The below config register a simple message broker (Note that it has nothing to do with authentication nor authorization).

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



Spring安全配置



由于Stomp协议依赖于第一个HTTP请求,我们需要授权对我们的stomp握手端点进行HTTP调用。

Spring security config

Since the Stomp protocol rely on a first HTTP Request, we'll need to authorize HTTP call to our stomp handshake endpoint.

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



然后我们' ll创建一个负责验证用户身份的服务。


Then we'll create a service responsible for authenticating users.

@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

Note that: UsernamePasswordAuthenticationToken MUST have GrantedAuthorities, if you use another constructor, Spring will auto-set isAuthenticated = false.



几乎在那里,现在我们需要创建一个Interceptor来设置 simpUser 标头或抛出< CONNECT消息上的code> AuthenticationException 。

@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( ) 必须返回 UsernamePasswordAuthenticationToken ,这是Spring安全链测试中的另一个元素。
请注意:如果您的 UsernamePasswordAuthenticationToken 是在未传递 GrantedAuthority 的情况下构建的,则身份验证将失败,因为构造函数未授权的权限自动设置 authenticated = false 这是重要的详细信息,未在spring-security中记录

Note that: preSend() MUST return a UsernamePasswordAuthenticationToken, another element in the spring security chain test this. Note that: If your UsernamePasswordAuthenticationToken was built without passing GrantedAuthority, the authentication will fail, because the constructor without granted authorities auto set authenticated = false THIS IS AN IMPORTANT DETAIL which is not documented in spring-security.



最后创建另外两个类来分别处理授权和身份验证。


Finally create two more class to handle respectively Authorization and Authentication.

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

}

请注意: @Order CRUCIAL 不要忘记它,它允许我们的拦截器首先在安全链上注册。

Note that: The @Order is CRUCIAL don't forget it, it allow our interceptor to be registered first on the security chain.


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

祝你好运!

这篇关于Spring中的Websocket身份验证和授权的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆