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:
We’ll implement a pre-filter in Zuul that runs before request routing. This filter will:
- Extract the JWT from the
Authorizationheader - Fetch the public key from Apigee’s
/v1/...endpoint - Validate the JWT’s signature using that public key
- 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
@EnableCachingon 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
Authorizationheader: Zuul should return a401 Unauthorizedresponse and block the request.
内容的提问来源于stack exchange,提问作者Sami

