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

Spring中验证Apigee生成的JWT令牌以保护Zuul服务器

Got it, let's walk through how to add JWT signature validation to your Zuul server so only valid requests get forwarded to your PCF API. Here's a practical, step-by-step solution tailored to your setup with Apigee's microgateway:

Solution Overview

We’ll implement a pre-filter in Zuul that runs before request routing. This filter will:

  1. Extract the JWT from the Authorization header
  2. Fetch the public key from Apigee’s /v1/... endpoint
  3. Validate the JWT’s signature using that public key
  4. Reject the request if validation fails, or allow it to proceed if successful

1. Add Required Dependencies

If you’re using Spring Boot (common with Zuul/Spring Cloud), add these dependencies to your pom.xml (or build.gradle equivalent) to handle JWT processing and HTTP calls:

<!-- JJWT library for JWT parsing/validation -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

<!-- Spring Web for calling Apigee's public key endpoint -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. Create the Zuul Pre-Filter

This is the core component that handles validation. Create a class that extends ZuulFilter:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Base64;

@Component
public class JwtValidationPreFilter extends ZuulFilter {

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    // Replace with your actual Apigee public key endpoint
    private static final String APIGEE_PUBLIC_KEY_URL = "/v1/your-public-key-endpoint";

    public JwtValidationPreFilter(RestTemplate restTemplate, ObjectMapper objectMapper) {
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE; // Run before request routing
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; // Ensure it runs before route setup
    }

    @Override
    public boolean shouldFilter() {
        // Optional: Add logic to only apply this filter to specific paths (e.g., /pcf-api/**)
        return true;
    }

    @Override
    public Object run() {
        var ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        // Extract the Authorization header
        String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            // Reject requests without a valid Bearer token
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            ctx.setResponseBody("Missing or invalid Authorization header (must be 'Bearer <JWT>')");
            return null;
        }

        String jwtToken = authHeader.substring(7); // Strip "Bearer " prefix

        try {
            // Fetch public key from Apigee
            String publicKeyString = fetchApigeePublicKey();
            // Convert Base64-encoded public key to a Key object
            byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
            Key publicKey = Keys.publicKeyFor(publicKeyBytes);

            // Validate JWT signature (add additional checks like expiry/issuer if needed)
            Jwts.parserBuilder()
                .setSigningKey(publicKey)
                .build()
                .parseClaimsJws(jwtToken);

            // Validation passed: let the request proceed to routing
            return null;
        } catch (Exception e) {
            // Validation failed: reject the request
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            ctx.setResponseBody("Invalid JWT signature: " + e.getMessage());
            return null;
        }
    }

    private String fetchApigeePublicKey() throws Exception {
        ResponseEntity<String> response = restTemplate.exchange(
            APIGEE_PUBLIC_KEY_URL,
            HttpMethod.GET,
            new HttpEntity<>(new HttpHeaders()),
            String.class
        );
        // Adjust this parsing logic to match Apigee's actual response format
        JsonNode responseBody = objectMapper.readTree(response.getBody());
        return responseBody.get("publicKey").asText();
    }
}

3. Register RestTemplate

Add a configuration class to create a RestTemplate bean (used to call Apigee's endpoint):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

4. Optional Optimizations

  • Cache the Public Key: Avoid calling Apigee’s endpoint on every request by caching the public key. Use Spring Cache to add a cache layer:

    // Add @Cacheable to the fetchApigeePublicKey method
    @Cacheable(value = "apigeePublicKey", expireAfterWrite = 3600) // Cache for 1 hour
    private String fetchApigeePublicKey() throws Exception {
        // Existing logic
    }
    

    Don’t forget to add Spring Cache dependencies and enable caching with @EnableCaching on your main application class.

  • Additional JWT Checks: Beyond signature validation, you can verify claims like expiry (exp), issuer (iss), or audience (aud) to tighten security:

    Jwts.parserBuilder()
        .setSigningKey(publicKey)
        .requireIssuer("your-apigee-issuer-id")
        .requireAudience("your-pcf-api-audience")
        .build()
        .parseClaimsJws(jwtToken);
    
  • Custom Error Responses: Replace the plain-text response body with a structured JSON response for better client integration.

5. Test the Setup

  • Send a request with a valid, Apigee-signed JWT: The request should be forwarded to your PCF API as expected.
  • Send a request with an invalid signature, expired JWT, or missing Authorization header: Zuul should return a 401 Unauthorized response and block the request.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:52:17