Spring中Websocket身份验证和授权的实现
作者:timi先生
在Web应用开发中,安全一直是非常重要的一个方面,本文主要介绍了Spring中Websocket身份验证和授权的实现,具有一定的参考价值,感兴趣的可以了解一下
一、需要了解的事项
- http和WebSocket的安全链和安全配置是完全独立的。
- SpringAuthenticationProvider根本不参与 Websocket 身份验证。
- 将要给出的示例中,身份验证不会发生在 HTTP 协商端点上,因为 JavaScript STOMP(websocket)库不会随 HTTP 请求一起发送必要的身份验证标头。
- 一旦在 CONNECT 请求上设置,用户( simpUser) 将被存储在 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 配置
3.1 、简单的消息代理
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(final MessageBrokerRegistry config) { config.enableSimpleBroker("/queue/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { registry.addEndpoint("stomp"); setAllowedOrigins("*") } }
3.2 、Spring安全配置
由于 Stomp 协议依赖于第一个 HTTP 请求,因此需要授权对 stomp 握手端点的 HTTP 调用。
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception http.httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/stomp").permitAll() .anyRequest().denyAll(); } }
然后创建一个负责验证用户身份的服务。
@Component public class WebSocketAuthenticatorService { 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."); } if (fetchUserFromDb(username, password) == null) { throw new BadCredentialsException("Bad credentials for user " + username); } return new UsernamePasswordAuthenticationToken( username, null, Collections.singleton((GrantedAuthority) () -> "USER") // 必须给至少一个角色 ); } }
接着需要创建一个拦截器,它将设置“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() 必须返回 UsernamePasswordAuthenticationToken,Spring 安全链中会对此进行测试。如果UsernamePasswordAuthenticationToken构建没有通过GrantedAuthority,则身份验证将失败,因为没有授予权限的构造函数自动设置authenticated = false 这是一个重要的细节,在 spring-security 中没有记录。
最后再创建两个类来分别处理授权和身份验证。
@Configuration @Order(Ordered.HIGHEST_PRECEDENCE + 99) public class WebSocketAuthenticationSecurityConfig extends WebSocketMessageBrokerConfigurer { @Inject private AuthChannelInterceptorAdapter authChannelInterceptorAdapter; @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // 这里不用给任何东西 } @Override public void configureClientInboundChannel(final ChannelRegistration registration) { registration.setInterceptors(authChannelInterceptorAdapter); } }
请注意:这@Order是至关重要的,它允许我们的拦截器首先在安全链中注册。
@Configuration public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) { // 添加自己的映射 messages.anyMessage().authenticated(); } // 这里请自己按需求修改 @Override protected boolean sameOriginDisabled() { return true; } }
之后编写客户端进行连接,我们就可以这样指定客户端进行消息的发送。
@MessageMapping("/greeting") public void greetingReturn(@Payload Object ojd){ simpMessagingTemplate.convertAndSendToUser(username,"/topic/greeting",ojd); }
到此这篇关于Spring中Websocket身份验证和授权的实现的文章就介绍到这了,更多相关Spring Websocket身份验证和授权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- Spring WebSocket 404错误的解决方法
- 完美解决spring websocket自动断开连接再创建引发的问题
- SpringBoot集成WebSocket实现前后端消息互传的方法
- SpringBoot+WebSocket+Netty实现消息推送的示例代码
- SpringBoot+Websocket实现一个简单的网页聊天功能代码
- springboot websocket简单入门示例
- SpringMVC整合websocket实现消息推送及触发功能
- Spring集成webSocket页面访问404问题的解决方法
- Spring Boot 开发私有即时通信系统(WebSocket)
- Spring Cloud微服务使用webSocket的方法
- java中实现兼容ie6 7 8 9的spring4+websocket