Spring Webflux应用JWT验证后获取Userinfo端点信息的最优方案
问题:Spring Webflux应用验证JWT后调用第三方Userinfo端点做校验的最优方式
我有一个暴露Webflux端点的Spring应用,使用JWT令牌对POST请求进行授权,但还需要获取Userinfo端点提供的信息。当前已配置SecurityWebFilterChain Bean并采用oauth2ResourceServer配置,需调用Userinfo端点做进一步校验。授权服务器为第三方服务,请问验证JWT令牌后获取Userinfo端点信息做后续校验的最优方式是什么?
附未调用Userinfo的安全配置代码:
@Bean public SecurityWebFilterChain filterChain(ServerHttpSecurity http) { http .cors() .and() .httpBasic().disable() .formLogin().disable() .csrf().disable() .logout().disable() .oauth2Client() .and() .authorizeExchange() .pathMatchers(HttpMethod.POST).authenticated() .anyExchange().permitAll() .and().oauth2ResourceServer().jwt() ; return http.build(); }
解决方案
在Spring Webflux的OAuth2资源服务器场景下,验证JWT后调用第三方Userinfo端点做校验的最优方案是自定义ReactiveAuthenticationManager,在原有JWT验证逻辑之后,追加Userinfo端点的调用与校验逻辑,同时将获取到的用户信息整合到认证对象中供后续使用。
步骤1:配置Userinfo端点信息
在application.yml中配置第三方授权服务器的Userinfo端点地址:
spring: security: oauth2: resourceserver: userinfo-uri: https://第三方授权服务器地址/userinfo
步骤2:自定义ReactiveAuthenticationManager
实现组合式认证管理器,先验证JWT有效性,再调用Userinfo端点执行校验:
@Component public class CustomReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final JwtReactiveAuthenticationManager jwtAuthManager; private final WebClient webClient; private final String userinfoUri; public CustomReactiveAuthenticationManager(JwtDecoder jwtDecoder, WebClient.Builder webClientBuilder, @Value("${spring.security.oauth2.resourceserver.userinfo-uri}") String userinfoUri) { this.jwtAuthManager = new JwtReactiveAuthenticationManager(jwtDecoder); this.webClient = webClientBuilder.build(); this.userinfoUri = userinfoUri; } @Override public Mono<Authentication> authenticate(Authentication authentication) { // 第一步:验证JWT合法性 return jwtAuthManager.authenticate(authentication) .flatMap(jwtAuth -> { // 从JWT中提取令牌 String token = ((Jwt) jwtAuth.getPrincipal()).getTokenValue(); // 第二步:调用Userinfo端点获取用户信息 return webClient.get() .uri(userinfoUri) .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .retrieve() .bodyToMono(UserInfoDTO.class) .map(userInfo -> { // 第三步:执行自定义校验逻辑 validateUserStatus(userInfo); // 将Userinfo信息整合到认证对象,供后续业务使用 List<GrantedAuthority> authorities = new ArrayList<>(jwtAuth.getAuthorities()); // 可添加Userinfo返回的角色/权限到authorities return new UsernamePasswordAuthenticationToken( userInfo, jwtAuth.getCredentials(), authorities ); }) .onErrorMap(e -> new AuthenticationServiceException("Userinfo校验失败", e)); }); } // 自定义校验规则,比如检查用户是否启用 private void validateUserStatus(UserInfoDTO userInfo) { if (!userInfo.isEnabled()) { throw new AuthenticationServiceException("用户已被禁用"); } } // 自定义Userinfo响应DTO public static class UserInfoDTO { private String username; private boolean enabled; private List<String> roles; // 省略getter、setter } }
步骤3:修改Security配置,替换认证管理器
将自定义的认证管理器配置到资源服务器中:
@Bean public SecurityWebFilterChain filterChain(ServerHttpSecurity http, CustomReactiveAuthenticationManager customAuthManager) { http .cors() .and() .httpBasic().disable() .formLogin().disable() .csrf().disable() .logout().disable() .oauth2Client() .and() .authorizeExchange() .pathMatchers(HttpMethod.POST).authenticated() .anyExchange().permitAll() .and() .oauth2ResourceServer() .authenticationManager(customAuthManager); // 使用自定义认证逻辑 return http.build(); }
补充优化建议
- 缓存优化:如果Userinfo调用频率高,可添加Redis缓存,缓存令牌对应的用户信息,设置与令牌过期时间匹配的缓存时长,减少第三方接口调用次数。
- 异常兜底:针对Userinfo端点的网络超时、返回错误码等情况,添加兜底处理逻辑,避免因第三方服务不可用导致认证失败。
内容的提问来源于stack exchange,提问作者Enthus
相关产品推荐
相关产品推荐

