You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Gateway自定义认证过滤器实现微服务鉴权咨询

嘿,我来帮你搞定这个Spring Gateway的JWT鉴权问题!自定义GatewayFilterFactory确实是官方推荐的标准方案,咱们一步步来实现:

实现思路与具体代码

1. 自定义JWT认证过滤器工厂

我们需要创建一个过滤器,负责从请求头提取JWT、调用认证服务验证、根据结果决定放行或返回404。

首先定义过滤器的配置类(用来传递认证服务地址等参数):

public class JwtAuthFilterConfig {
    private String authServiceUrl;

    // 构造器、Getter、Setter
    public JwtAuthFilterConfig(String authServiceUrl) {
        this.authServiceUrl = authServiceUrl;
    }

    public String getAuthServiceUrl() {
        return authServiceUrl;
    }
}

然后实现过滤器工厂:

@Component
public class JwtAuthenticationFilterFactory extends AbstractGatewayFilterFactory<JwtAuthFilterConfig> {

    private final WebClient webClient;

    // 注入WebClient.Builder,自动支持负载均衡
    public JwtAuthenticationFilterFactory(WebClient.Builder webClientBuilder) {
        super(JwtAuthFilterConfig.class);
        this.webClient = webClientBuilder.build();
    }

    @Override
    public GatewayFilter apply(JwtAuthFilterConfig config) {
        return (exchange, chain) -> {
            // 1. 从请求头获取Authorization令牌
            String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                return build404Response(exchange, "Missing or invalid authentication token");
            }

            String jwtToken = authHeader.substring(7); // 去掉"Bearer "前缀

            // 2. 调用认证服务的验证接口(假设认证服务提供POST /auth/validate接口)
            return webClient.post()
                    .uri(config.getAuthServiceUrl() + "/auth/validate")
                    .bodyValue(jwtToken)
                    .retrieve()
                    .bodyToMono(Boolean.class)
                    .flatMap(isValid -> {
                        if (Boolean.TRUE.equals(isValid)) {
                            // 验证通过,放行请求到目标微服务
                            return chain.filter(exchange);
                        } else {
                            // 验证失败,返回404
                            return build404Response(exchange, "Invalid JWT token");
                        }
                    })
                    .onErrorResume(e -> {
                        // 调用认证服务出错时,也返回404
                        return build404Response(exchange, "Authentication service unavailable");
                    });
        };
    }

    // 构建404错误响应
    private Mono<Void> build404Response(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.NOT_FOUND);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        String jsonBody = String.format("{\"error\": \"%s\"}", message);
        DataBuffer buffer = response.bufferFactory().wrap(jsonBody.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    // 配置快捷参数,方便在路由中直接使用
    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("authServiceUrl");
    }
}

2. 在路由配置中使用过滤器

现在把这个过滤器应用到你的受保护微服务路由上:

// 假设你的负载均衡服务名定义如下
private static final String LOAD_BALANCED_AUTHENTICATION_SERVICE = "lb://authentication-service";
private static final String LOAD_BALANCED_IMAGES_SERVICE = "lb://images-service";
private static final String LOAD_BALANCED_INBOX_SERVICE = "lb://inbox-service";

// 注入WebClient.Builder和自定义过滤器工厂
@Autowired
private WebClient.Builder webClientBuilder;

@Autowired
private JwtAuthenticationFilterFactory jwtAuthFilterFactory;

// 路由配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(route -> route.path("/auth/**")
                    .uri(LOAD_BALANCED_AUTHENTICATION_SERVICE)
                    .id("authentication-service"))
            .route(route -> route.path("/images/**")
                    .filters(f -> f.filter(jwtAuthFilterFactory.apply(
                            new JwtAuthFilterConfig(LOAD_BALANCED_AUTHENTICATION_SERVICE))))
                    .uri(LOAD_BALANCED_IMAGES_SERVICE)
                    .id("images-service"))
            .route(route -> route.path("/inbox/**")
                    .filters(f -> f.filter(jwtAuthFilterFactory.apply(
                            new JwtAuthFilterConfig(LOAD_BALANCED_AUTHENTICATION_SERVICE))))
                    .uri(LOAD_BALANCED_INBOX_SERVICE)
                    .id("inbox-service"))
            .build();
}

或者利用shortcutFieldOrder的配置,用更简洁的方式调用过滤器:

.route(route -> route.path("/images/**")
        .filters(f -> f.jwtAuthentication(LOAD_BALANCED_AUTHENTICATION_SERVICE))
        .uri(LOAD_BALANCED_IMAGES_SERVICE)
        .id("images-service"))

3. 认证服务配合

确保你的认证服务提供一个验证JWT的接口,比如:

@PostMapping("/auth/validate")
public ResponseEntity<Boolean> validateToken(@RequestBody String token) {
    // 这里实现JWT的验证逻辑,比如解析令牌、检查签名和过期时间等
    boolean isValid = jwtUtil.validateToken(token);
    return ResponseEntity.ok(isValid);
}

关键细节说明

  • WebClient的使用:因为Spring Gateway基于WebFlux响应式框架,所以用WebClient调用认证服务比RestTemplate更合适,而且它自动支持Spring Cloud的负载均衡。
  • 请求终止与放行:通过build404Response直接返回响应,终止请求链;验证通过则调用chain.filter(exchange)放行请求到目标微服务。
  • 错误处理:调用认证服务失败时(比如服务宕机),也返回404,符合你的需求。

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

火山引擎 最新活动