Spring中的Websocket身份验证和授权

我一直在努力用Spring-Security 正确实现Stomp(websocket) 和 。

为了后代,我将回答我自己的问题以提供指导。

问题

Spring WebSocket文档(用于身份验证)看起来不清楚ATM(IMHO)。而且我不明白如何正确处理 和 。

我想要的是

  • 使用登录名/密码对用户进行身份验证。
  • 防止匿名用户通过WebSocket连接。
  • 添加授权层(用户,管理员等)。
  • Principal在控制器可用。

我不想要的

  • 在HTTP协商端点上进行身份验证(因为大多数JavaScript库都不随HTTP协商调用一起发送身份验证标头)。

回答:

如上所述,文档(ATM)尚不清楚(IMHO),直到Spring提供一些清晰的文档为止,这是一个样板,可避免您花两天的时间试图了解安全链的功能。

Rob-Leggett做出了非常不错的尝试,但他在Springs上课了,所以我感到不舒服。

要知道的事情:

  • __http和WebSocket的 安全链安全配置 是完全独立的。
  • Spring AuthenticationProvider完全不参与Websocket身份验证。
  • 不会在HTTP协商终结点上进行身份验证,因为没有JavaScript STOMP(Websocket)将身份验证标头与HTTP请求一起发送。
  • 在CONNECT请求上设置后, 用户simpUser)将存储在websocket会话中,以后的消息将不再需要身份验证。

Maven部门

<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配置

下面的配置注册一个简单的消息代理(请注意,它与身份验证或授权无关)。

@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请求,因此我们需要授权对脚踏握手端点的HTTP调用。

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

}

}

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

@Component

public class WebSocketAuthenticatorService {

// This method MUST return a UsernamePasswordAuthenticationToken instance, the spring security chain is testing it with 'instanceof' later on. So don't use a subclass of it or any other class

public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username, final String password) throws AuthenticationException {

if (username == null || username.trim().isEmpty()) {

throw new AuthenticationCredentialsNotFoundException("Username was null or empty.");

}

if (password == null || password.trim().isEmpty()) {

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

几乎在那儿,现在我们需要创建一个拦截器,该拦截器将设置“ simpUser”标头或在CONNECT消息上抛出“

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() 返回a

UsernamePasswordAuthenticationToken,Spring安全链中的另一个元素对此进行测试。请注意:如果您的UsernamePasswordAuthenticationToken构建未通过GrantedAuthority,则身份验证将失败,因为未经授权的构造函数会自动设置“

authenticated = false

最后,再创建两个类以分别处理授权和身份验证。

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

}

}

请注意:@Orderis 别忘了它,它使我们的拦截器可以在安全链中首先注册。

@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身份验证和授权 的全部内容, 来源链接: utcz.com/qa/400413.html

回到顶部