Spring Boot中RabbitMQ MessageListener的SecurityContextHolder返回null问题
解决RabbitMQ MessageListener中SecurityContextHolder获取不到上下文的问题
问题原因
RabbitMQ的消息监听线程属于独立线程池,不会自动继承Web请求线程(或其他业务线程)的SecurityContext,所以onMessage方法里调用SecurityContextHolder.getContext()会返回null;而Web层等其他业务代码运行在Spring Security管理的线程中,能正常获取上下文。
解决方案
方案一:配置SecurityContext自动传播(需发送端配合)
通过配置RabbitMQ监听器容器,让监听线程继承发送端的SecurityContext,需发送端将上下文存入消息头,接收端配置拦截器自动解析:
- 接收端配置类:
@Configuration public class RabbitMQConfig { @Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory, SecurityContextPropagationChannelInterceptor securityContextInterceptor) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); // 添加SecurityContext传播拦截器 factory.setAfterReceivePostProcessors(securityContextInterceptor); return factory; } @Bean public SecurityContextPropagationChannelInterceptor securityContextPropagationChannelInterceptor() { return new SecurityContextPropagationChannelInterceptor(); } }
- 发送端发送消息时携带SecurityContext:
// 获取当前上下文 SecurityContext securityContext = SecurityContextHolder.getContext(); // 构造消息头 MessageHeaders headers = new MessageHeaders(Map.of( SecurityContextPropagationChannelInterceptor.SECURITY_CONTEXT_HEADER, securityContext )); // 构造消息并发送 Message<Facture> message = MessageBuilder.createMessage(facture, headers); rabbitTemplate.send("exchange-name", "routing-key", message);
方案二:手动设置固定身份(无需发送端配合)
如果消息处理不需要依赖发送端用户,可在onMessage方法开头手动设置系统用户的认证信息,处理完成后清空上下文避免线程复用问题:
@Override public void onMessage(Message message) { // 构造系统用户认证信息,替换为你的实际用户数据 ConnectedUser systemUser = new ConnectedUser("system_user", "System", "Admin"); Authentication auth = new UsernamePasswordAuthenticationToken( systemUser, null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_SYSTEM")) ); // 设置上下文 SecurityContextHolder.getContext().setAuthentication(auth); try { Gson gson = new Gson(); ControleResult resultControle = gson.fromJson(new String(message.getBody()), Facture.class); Optional<Facture> optional = dossierDao.findById(resultControle.getId()); Facture facture= null; if (optional.isPresent()) { facture= optional.get(); } factureDao.save(facture); log.info("USER " + getLoggedInUserName()); } finally { // 清空上下文,防止线程池复用导致的上下文污染 SecurityContextHolder.clearContext(); } }
方案三:使用@RabbitListener时开启自动传播
如果项目用@RabbitListener注解替代直接实现MessageListener,可直接在配置文件中开启SecurityContext传播:
在application.properties中添加:
# 针对simple类型监听器容器 spring.rabbitmq.listener.simple.transmit-security-context=true # 针对direct类型监听器容器(如果使用的话) spring.rabbitmq.listener.direct.transmit-security-context=true
同样需要发送端按照方案一的方式将SecurityContext存入消息头。
内容的提问来源于stack exchange,提问作者MED
相关产品推荐
相关产品推荐

