ZBLOG

spring security 接入 websocket + stomp 需要添加哪些配置以及socket用到的用户信息、session信息怎么从security 对应过来

在使用Spring Security接入WebSocket和STOMP时,需要进行以下配置:

  1. 配置WebSocket

WebSocketConfig中,我们需要将Spring Security的ChannelInterceptorAdapter添加到MessageBrokerRegistry中。这样可以在消息发送之前进行身份验证,并允许只有经过身份验证的用户才能发送或接收消息。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Autowired
    private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*")
                .addInterceptors(webSocketHandshakeInterceptor).withSockJS();
    }
}

其中,我们定义了一个自定义的WebSocketHandshakeInterceptor用于对握手请求进行拦截处理。

  1. 配置Security

首先,需要配置Spring Security以启用WebSockets支持:

@Configuration
@EnableWebSecurity
public class WebSocketSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/ws/**").authenticated()
                .anyRequest().permitAll()
            .and()
            .formLogin();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER");
    }
}

在上面的示例中,我们禁用了CSRF保护,并定义了针对/ws/**路径的安全规则。只有经过身份验证的用户才能访问此路径。我们还配置了一个内存中的用户,用户名为”user”,密码为”password”。

  1. 配置自定义握手拦截器
@Component
public class WebSocketHandshakeInterceptor extends ChannelInterceptorAdapter implements HandshakeInterceptor {

    private static final Logger LOG = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        LOG.info("Before Handshake");

        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession();
            attributes.put("sessionId", session.getId());
        }

        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception ex) {
        LOG.info("After Handshake");
    }

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String sessionId = (String) accessor.getSessionAttributes().get("sessionId");
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth == null) {
                throw new AccessDeniedException("Access is denied");
            } else {
                accessor.setUser(new User(auth.getName(), "", auth.getAuthorities()));
                accessor.setSessionId(sessionId);
            }
        }

        return message;
    }
}

上述示例中,我们定义了一个自定义的WebSocketHandshakeInterceptor,用于对握手请求进行拦截处理。在beforeHandshake()方法中,我们将HttpSession Id存储到WebSocket连接属性中。

然后,在preSend()方法中,我们从StompHeaderAccessor获取会话ID并与当前SecurityContext中的用户关联。如果没有找到身份验证用户,则抛出AccessDeniedException。

  1. 配置Stomp控制器
@Controller
public class WebSocketController {

    @MessageMapping("/hello")
    @SendToUser("/topic/greetings")
    public Greeting greeting(HelloMessage message, Principal principal) throws Exception {
        Thread.sleep(1000);
        return new Greeting("Hello, " + message.getName() + "!", principal.getName());
    }
}

class HelloMessage {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class Greeting {

    private String content;
    private String user;

    public Greeting(String content, String user) {
        this.content = content;
        this.user = user;
    }

    public String getContent() {
        return content;
    }

  	public void setContent(String content) {
      	this.content = content;  
  	}

  	public String getUser() {
    	return user;
  	}

  	public void setUser(String user) {
    	this.user = user;
  	}
}

在上面的示例中,我们定义了一个@MessageMapping注释的方法来接收客户端发送的消息。然后,使用@SendToUser注释将响应发送到指定用户的订阅路径。

在这个例子中,我们还定义了一个Greeting类,用于存储消息内容和用户信息。

其中,在STOMP协议的CONNECTED帧中包含有关已连接用户的身份验证信息。Principal对象包含与该用户相关联的名称和权限。因此,在Greeting类构造函数中,我们将其作为参数传递并存储到user属性中。

  1. 用户信息对应

通过上述示例可知,Spring Security授权成功后将会返回一个Principal对象,在WebSocket控制器方法签名处使用@MessageMapping注解接收请求时可以直接获取当前用户信息。

例如:

public Greeting greeting(HelloMessage message, Principal principal) throws Exception {
    Thread.sleep(1000);
    return new Greeting("Hello, " + message.getName() + "!", principal.getName());
}

在上面的例子中,principal.getName()返回当前用户的用户名。如果您需要更多的用户详细信息,则可以从SecurityContext获取Authentication对象,并从该对象获取任何其他属性或详细信息。

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://golang.0voice.com/?id=4811

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?