MuzySuntree 2025-04-09 13:09 采纳率: 0%
浏览 19

SpringSecurity WebFlux的findByUsername使用ReactiveFeignClient出现问题

微服务项目中,我使用webflux的SpringSecurity部署在网关。在findByUsername函数里面,我调用ReactiveFeignClient接口去其他微服务那里根据用户名获取用户信息,但没有任何信息,ReactiveFeignClient不报错也不返回任何信息。我又将findByUsername函数里面的代码转移到controller里面进行测试调用,发现可以获取到用户信息。这是什么情况,ReactiveFeignClient不能够在findByUsername函数里面使用吗?
UserDetailsServiceImpl代码:

package com.example.servicegateway.service.impl;

import com.example.servicegateway.entity.LoginUser;
import com.example.servicegateway.entity.Users;
import com.example.servicegateway.exception.LoginErrorException;
import com.example.servicegateway.feign.FeignPermission;
import com.example.servicegateway.feign.FeignRolePermission;
import com.example.servicegateway.feign.FeignUsers;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@Service
public class UserDetailsServiceImpl implements ReactiveUserDetailsService {
 
    private final FeignUsers feignUsers;
    private final FeignRolePermission rolePermissionFeign;
    private final FeignPermission permissionFeign;

    public UserDetailsServiceImpl(FeignUsers feignUsers, FeignRolePermission rolePermissionFeign, FeignPermission permissionFeign) {
        this.feignUsers = feignUsers;
        this.rolePermissionFeign = rolePermissionFeign;
        this.permissionFeign = permissionFeign;
    }


    @Override
    public Mono<UserDetails> findByUsername(String username) {
        System.out.println("收到用户名:" + username);
        return feignUsers.selectOneByName(username)
                .flatMap(usersApiResponse -> {
                    if (usersApiResponse.getCode() == 200) {
                        Users user = usersApiResponse.getData();
                        System.out.println("用户数据:" + user);
                        return rolePermissionFeign.selectByRoleId(user.getRoleid())
                                .doOnSubscribe(s -> System.out.println("rolePermissionFeign已订阅"))
                                .doOnNext(rp -> System.out.println("rolePermissionFeign 返回: " + rp))
                                .flatMapMany(rolePermissionApiResponse -> Flux.fromIterable(rolePermissionApiResponse.getData()))
                                .doOnNext(rp -> System.out.println("单个角色权限: " + rp))
                                .flatMap(rolePermission ->
                                        permissionFeign.selectOne(rolePermission.getPermissionid())
                                                .doOnNext(pr -> System.out.println("permissionFeign 返回: " + pr))
                                )
                                .map(permissionApiResponse -> permissionApiResponse.getData().getName())
                                .collectList()
                                .map(permissions -> new LoginUser(user, permissions));
                    } else {
                        return Mono.error(new LoginErrorException(usersApiResponse.getMessage()));
                    }
                })
                .doOnError(e -> System.out.println("出错了:" + e.getMessage()))
                .cast(UserDetails.class)
                .doFinally(signalType -> System.out.println("链路结束:" + signalType));
    }
}

img

  • 写回答

5条回答 默认 最新

  • 一杯年华@编程空间 2025-05-13 23:55
    关注

    [ 一杯年华@编程空间]帮您解答,本答案参考 ChatGPT4.0。

    我曾经遇到过类似的问题,在使用SpringSecurity WebFlux时,在findByUsername函数里通过ReactiveFeignClient调用其他微服务获取用户信息却没有结果,但在controller中调用相同代码却可以获取到信息。

    问题分析

    1. 上下文差异findByUsername方法在Spring Security的特定上下文中执行,与controller的执行上下文不同,可能存在某些过滤器或处理器影响了异步操作的执行。
    2. 错误处理:在findByUsername方法中的错误处理逻辑可能覆盖了异常,导致没有正确返回数据,并且doOnError等方法调试时,可能没有传递关键信息。
    3. Mono/Flux处理不当:处理FluxMono时,链式调用可能存在问题,导致最终没有触发订阅,从而没有执行网络请求。
    4. 超时问题:认证流程可能存在默认的超时设置,导致请求未完成就被中断。

    解决方案

    1. 确保正确的订阅和链式调用:在响应式流的末尾添加有效的订阅操作,保证异步调用正常开启。
      @Override
      public Mono<UserDetails> findByUsername(String username) {
       return feignUsers.selectOneByName(username)
          .flatMap(usersApiResponse -> {
               // 原有逻辑
           })
          .subscribeOn(Schedulers.boundedElastic())
          .subscribe();
      }
      
    2. 增加调试信息:在链式调用中添加更多doOnNextdoOnError等方法来输出调试信息,便于了解数据流状态,定位问题。
      @Override
      public Mono<UserDetails> findByUsername(String username) {
       return feignUsers.selectOneByName(username)
          .doOnNext(response -> System.out.println("收到用户服务响应: " + response))
          .flatMap(usersApiResponse -> {
               // 原有逻辑
           })
          .doOnError(e -> System.err.println("出错了:" + e.getMessage()))
          .cast(UserDetails.class)
          .doFinally(signalType -> System.out.println("链路结束:" + signalType));
      }
      
    3. 使用onErrorReturnonErrorResume处理异常:保证即使发生错误,也能给出反馈,便于调试。
      @Override
      public Mono<UserDetails> findByUsername(String username) {
       return feignUsers.selectOneByName(username)
          .onErrorResume(throwable -> {
               System.err.println("发生错误: " + throwable.getMessage());
               return Mono.empty();
           })
          .flatMap(usersApiResponse -> {
               // 原有逻辑
           });
      }
      
    4. 添加超时控制:设置合理的超时时间,避免因认证流程的默认超时导致请求中断。
      @Override
      public Mono<UserDetails> findByUsername(String username) {
       return feignUsers.selectOneByName(username)
          .timeout(Duration.ofSeconds(5))
          .flatMap(usersApiResponse -> {
               // 原有逻辑
           });
      }
      

    最优方案讲解

    综合来看,“增加调试信息”并结合“使用onErrorReturnonErrorResume处理异常”的方案是相对最优的。

    增加调试信息可以让我们清晰地了解响应式流中每一步的执行情况,包括何时接收到响应、在哪个环节出现错误等。通过doOnNextdoOnError等方法打印详细信息,能快速定位到问题所在,比如是在feignUsers.selectOneByName(username)调用时就没有获取到数据,还是在后续处理rolePermissionFeignpermissionFeign时出现异常。

    同时,使用onErrorReturnonErrorResume处理异常,能保证在出现错误时不会让整个流程中断而没有任何反馈。可以在捕获到异常时,记录详细的错误信息,并且返回一个默认值或者进行其他合理的处理,这样不仅有利于调试,还能增强系统的健壮性,即使在网络波动或微服务暂时不可用等异常情况下,也能给用户提供相对友好的提示,而不是直接返回无数据的结果。

    评论

报告相同问题?

问题事件

  • 创建了问题 4月9日