You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud网关如何携带Basic Auth认证信息路由请求至微服务

Basic Auth网关认证后微服务获取认证信息问题解决方案

问题描述

我拥有Gateway和HelloWorld两个微服务,在Gateway中使用Basic Auth完成用户认证,认证通过后请求会路由至HelloWorld微服务,但存在认证信息未同步传递的问题:HelloWorld的/secured接口中Authentication参数为null,触发空指针异常。

HelloWorld核心代码

@RequestMapping("/")
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, world!";
    }

    @GetMapping("/secured")
    public String secured(Authentication auth) {
        // !!! auth == null -> NullPointerException !!!
        return "This page is secured. Your role is "
                + auth.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(", ")) + ".";
    }
}

Gateway安全配置代码

@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManager authManager) throws Exception {
        return http
                .authorizeExchange(request -> request
                        .pathMatchers("/hello").permitAll()
                        .pathMatchers("/secured").authenticated()
                        .anyExchange().permitAll()
                )
                .csrf(csrf -> csrf.disable())
                .httpBasic(httpBasic -> httpBasic.authenticationManager(authManager))
                .build();
    }

    // 其他安全配置...
}

Gateway路由配置代码

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

    @Bean
    public RouteLocator myRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(p -> p
                        .path("/**")
                        .uri("http://localhost:8081"))
                .build();
    }
}

问题解答

1. 如何在Gateway中传递Authentication信息至微服务,避免重复认证

可以通过网关添加全局过滤器传递认证信息,再在微服务中解析并构建Security上下文的方式解决,具体步骤如下:

步骤1:Gateway中添加全局过滤器,注入认证信息到请求头

创建全局过滤器,从网关的Security上下文提取已认证的用户信息和权限,放入自定义请求头中:

@Component
public class AuthenticationHeaderFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return exchange.getPrincipal()
                .cast(Authentication.class)
                .flatMap(auth -> {
                    // 注入用户名到请求头
                    ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
                            .header("X-User-Name", auth.getName())
                            // 注入权限列表(逗号分隔)到请求头
                            .header("X-User-Authorities", auth.getAuthorities().stream()
                                    .map(GrantedAuthority::getAuthority)
                                    .collect(Collectors.joining(",")))
                            .build();
                    return chain.filter(exchange.mutate().request(modifiedRequest).build());
                })
                // 无认证信息的请求(如/hello)直接放行
                .switchIfEmpty(chain.filter(exchange));
    }
}

步骤2:HelloWorld微服务中添加过滤器,构建Security上下文

创建过滤器,从请求头提取认证信息,构建Authentication对象并放入Security上下文,这样接口的Authentication参数就会被Spring自动注入:

@Component
public class AuthenticationContextFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String userName = request.getHeader("X-User-Name");
        String authoritiesStr = request.getHeader("X-User-Authorities");
        
        if (userName != null && authoritiesStr != null) {
            // 解析权限列表
            List<GrantedAuthority> authorities = Arrays.stream(authoritiesStr.split(","))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
            // 构建认证对象
            Authentication auth = new UsernamePasswordAuthenticationToken(userName, null, authorities);
            // 放入Security上下文
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }
}

步骤3:HelloWorld微服务调整Security配置

关闭微服务自身的Basic Auth认证(由网关统一处理),同时确保接口权限校验正常:

@Configuration
@EnableWebSecurity
public class HelloWorldSecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/hello").permitAll()
                        .requestMatchers("/secured").authenticated()
                        .anyRequest().permitAll()
                )
                .httpBasic(httpBasic -> httpBasic.disable())
                .csrf(csrf -> csrf.disable())
                .build();
    }
}

2. 统一在Gateway做认证,还是每个微服务重复实现更常见

统一在Gateway做认证是微服务架构中更常见的最佳实践,原因如下:

  • 减少重复代码:无需在每个微服务中重复编写认证逻辑,降低维护成本和代码冗余。
  • 集中管控:认证规则、权限策略可在网关统一配置,修改时只需调整网关,无需逐个更新微服务。
  • 性能优化:避免每个微服务重复执行认证流程,减少系统整体开销。
  • 安全边界清晰:网关作为入口统一拦截未认证请求,微服务可专注于业务逻辑;同时可配置微服务仅允许来自网关的请求,提升安全性。

需要注意的是:微服务不能完全依赖网关的认证,建议添加二次校验机制,比如配置IP白名单仅允许网关IP访问,或者对传递的认证信息添加签名校验,防止绕过网关直接访问微服务的风险。


内容的提问来源于stack exchange,提问作者Arkadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 14:22:20