Overview
API Gateway Authorizers provide authentication and authorization for API endpoints. This implementation covers various authorizer types including JWT, Lambda, Custom, and Cognito for AWS API Gateway.
1. Dependencies
<dependencies> <!-- AWS SDK --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>3.11.2</version> </dependency> <!-- JWT --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> </dependencies>
2. Core Domain Models
API Gateway Events
package com.example.apigateway.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; import java.util.List; public class APIGatewayRequest { @JsonProperty("resource") private String resource; @JsonProperty("path") private String path; @JsonProperty("httpMethod") private String httpMethod; @JsonProperty("headers") private Map<String, String> headers; @JsonProperty("queryStringParameters") private Map<String, String> queryStringParameters; @JsonProperty("pathParameters") private Map<String, String> pathParameters; @JsonProperty("stageVariables") private Map<String, String> stageVariables; @JsonProperty("requestContext") private RequestContext requestContext; @JsonProperty("body") private String body; @JsonProperty("isBase64Encoded") private boolean isBase64Encoded; // Getters and Setters public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getHttpMethod() { return httpMethod; } public void setHttpMethod(String httpMethod) { this.httpMethod = httpMethod; } public Map<String, String> getHeaders() { return headers; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } public Map<String, String> getQueryStringParameters() { return queryStringParameters; } public void setQueryStringParameters(Map<String, String> queryStringParameters) { this.queryStringParameters = queryStringParameters; } public Map<String, String> getPathParameters() { return pathParameters; } public void setPathParameters(Map<String, String> pathParameters) { this.pathParameters = pathParameters; } public Map<String, String> getStageVariables() { return stageVariables; } public void setStageVariables(Map<String, String> stageVariables) { this.stageVariables = stageVariables; } public RequestContext getRequestContext() { return requestContext; } public void setRequestContext(RequestContext requestContext) { this.requestContext = requestContext; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public boolean isBase64Encoded() { return isBase64Encoded; } public void setBase64Encoded(boolean base64Encoded) { isBase64Encoded = base64Encoded; } } class RequestContext { @JsonProperty("accountId") private String accountId; @JsonProperty("apiId") private String apiId; @JsonProperty("protocol") private String protocol; @JsonProperty("httpMethod") private String httpMethod; @JsonProperty("path") private String path; @JsonProperty("stage") private String stage; @JsonProperty("requestId") private String requestId; @JsonProperty("requestTime") private String requestTime; @JsonProperty("requestTimeEpoch") private long requestTimeEpoch; @JsonProperty("identity") private Identity identity; @JsonProperty("authorizer") private Map<String, Object> authorizer; // Getters and Setters public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public String getApiId() { return apiId; } public void setApiId(String apiId) { this.apiId = apiId; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public String getHttpMethod() { return httpMethod; } public void setHttpMethod(String httpMethod) { this.httpMethod = httpMethod; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getStage() { return stage; } public void setStage(String stage) { this.stage = stage; } public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getRequestTime() { return requestTime; } public void setRequestTime(String requestTime) { this.requestTime = requestTime; } public long getRequestTimeEpoch() { return requestTimeEpoch; } public void setRequestTimeEpoch(long requestTimeEpoch) { this.requestTimeEpoch = requestTimeEpoch; } public Identity getIdentity() { return identity; } public void setIdentity(Identity identity) { this.identity = identity; } public Map<String, Object> getAuthorizer() { return authorizer; } public void setAuthorizer(Map<String, Object> authorizer) { this.authorizer = authorizer; } } class Identity { @JsonProperty("cognitoIdentityPoolId") private String cognitoIdentityPoolId; @JsonProperty("accountId") private String accountId; @JsonProperty("cognitoIdentityId") private String cognitoIdentityId; @JsonProperty("caller") private String caller; @JsonProperty("apiKey") private String apiKey; @JsonProperty("sourceIp") private String sourceIp; @JsonProperty("cognitoAuthenticationType") private String cognitoAuthenticationType; @JsonProperty("cognitoAuthenticationProvider") private String cognitoAuthenticationProvider; @JsonProperty("userArn") private String userArn; @JsonProperty("userAgent") private String userAgent; @JsonProperty("user") private String user; // Getters and Setters public String getCognitoIdentityPoolId() { return cognitoIdentityPoolId; } public void setCognitoIdentityPoolId(String cognitoIdentityPoolId) { this.cognitoIdentityPoolId = cognitoIdentityPoolId; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public String getCognitoIdentityId() { return cognitoIdentityId; } public void setCognitoIdentityId(String cognitoIdentityId) { this.cognitoIdentityId = cognitoIdentityId; } public String getCaller() { return caller; } public void setCaller(String caller) { this.caller = caller; } public String getApiKey() { return apiKey; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public String getSourceIp() { return sourceIp; } public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; } public String getCognitoAuthenticationType() { return cognitoAuthenticationType; } public void setCognitoAuthenticationType(String cognitoAuthenticationType) { this.cognitoAuthenticationType = cognitoAuthenticationType; } public String getCognitoAuthenticationProvider() { return cognitoAuthenticationProvider; } public void setCognitoAuthenticationProvider(String cognitoAuthenticationProvider) { this.cognitoAuthenticationProvider = cognitoAuthenticationProvider; } public String getUserArn() { return userArn; } public void setUserArn(String userArn) { this.userArn = userArn; } public String getUserAgent() { return userAgent; } public void setUserAgent(String userAgent) { this.userAgent = userAgent; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } } Authorizer Models
package com.example.apigateway.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; public class AuthorizerResponse { @JsonProperty("principalId") private String principalId; @JsonProperty("policyDocument") private PolicyDocument policyDocument; @JsonProperty("context") private Map<String, String> context; @JsonProperty("usageIdentifierKey") private String usageIdentifierKey; // Getters and Setters public String getPrincipalId() { return principalId; } public void setPrincipalId(String principalId) { this.principalId = principalId; } public PolicyDocument getPolicyDocument() { return policyDocument; } public void setPolicyDocument(PolicyDocument policyDocument) { this.policyDocument = policyDocument; } public Map<String, String> getContext() { return context; } public void setContext(Map<String, String> context) { this.context = context; } public String getUsageIdentifierKey() { return usageIdentifierKey; } public void setUsageIdentifierKey(String usageIdentifierKey) { this.usageIdentifierKey = usageIdentifierKey; } } class PolicyDocument { @JsonProperty("Version") private String version; @JsonProperty("Statement") private Statement[] statement; // Getters and Setters public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public Statement[] getStatement() { return statement; } public void setStatement(Statement[] statement) { this.statement = statement; } } class Statement { @JsonProperty("Action") private String action; @JsonProperty("Effect") private String effect; @JsonProperty("Resource") private String resource; @JsonProperty("Condition") private Map<String, Object> condition; // Getters and Setters public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getEffect() { return effect; } public void setEffect(String effect) { this.effect = effect; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public Map<String, Object> getCondition() { return condition; } public void setCondition(Map<String, Object> condition) { this.condition = condition; } } public class TokenAuthorizerRequest { @JsonProperty("type") private String type; @JsonProperty("authorizationToken") private String authorizationToken; @JsonProperty("methodArn") private String methodArn; // Getters and Setters public String getType() { return type; } public void setType(String type) { this.type = type; } public String getAuthorizationToken() { return authorizationToken; } public void setAuthorizationToken(String authorizationToken) { this.authorizationToken = authorizationToken; } public String getMethodArn() { return methodArn; } public void setMethodArn(String methodArn) { this.methodArn = methodArn; } } public class RequestAuthorizerRequest { @JsonProperty("type") private String type; @JsonProperty("methodArn") private String methodArn; @JsonProperty("identitySource") private String identitySource; @JsonProperty("headers") private Map<String, String> headers; @JsonProperty("queryStringParameters") private Map<String, String> queryStringParameters; @JsonProperty("pathParameters") private Map<String, String> pathParameters; @JsonProperty("stageVariables") private Map<String, String> stageVariables; @JsonProperty("requestContext") private Map<String, Object> requestContext; // Getters and Setters public String getType() { return type; } public void setType(String type) { this.type = type; } public String getMethodArn() { return methodArn; } public void setMethodArn(String methodArn) { this.methodArn = methodArn; } public String getIdentitySource() { return identitySource; } public void setIdentitySource(String identitySource) { this.identitySource = identitySource; } public Map<String, String> getHeaders() { return headers; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } public Map<String, String> getQueryStringParameters() { return queryStringParameters; } public void setQueryStringParameters(Map<String, String> queryStringParameters) { this.queryStringParameters = queryStringParameters; } public Map<String, String> getPathParameters() { return pathParameters; } public void setPathParameters(Map<String, String> pathParameters) { this.pathParameters = pathParameters; } public Map<String, String> getStageVariables() { return stageVariables; } public void setStageVariables(Map<String, String> stageVariables) { this.stageVariables = stageVariables; } public Map<String, Object> getRequestContext() { return requestContext; } public void setRequestContext(Map<String, Object> requestContext) { this.requestContext = requestContext; } } 3. Base Authorizer Interface
package com.example.apigateway.authorizer; import com.example.apigateway.model.AuthorizerResponse; public interface Authorizer { AuthorizerResponse authorize(AuthorizerRequest request); } abstract class AuthorizerRequest { protected String methodArn; protected String type; // Getters and Setters public String getMethodArn() { return methodArn; } public void setMethodArn(String methodArn) { this.methodArn = methodArn; } public String getType() { return type; } public void setType(String type) { this.type = type; } } class TokenAuthorizerRequest extends AuthorizerRequest { private String authorizationToken; public String getAuthorizationToken() { return authorizationToken; } public void setAuthorizationToken(String authorizationToken) { this.authorizationToken = authorizationToken; } } class RequestAuthorizerRequest extends AuthorizerRequest { private Map<String, String> headers; private Map<String, String> queryStringParameters; private Map<String, String> pathParameters; private Map<String, String> stageVariables; private Map<String, Object> requestContext; private String identitySource; // Getters and Setters public Map<String, String> getHeaders() { return headers; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } public Map<String, String> getQueryStringParameters() { return queryStringParameters; } public void setQueryStringParameters(Map<String, String> queryStringParameters) { this.queryStringParameters = queryStringParameters; } public Map<String, String> getPathParameters() { return pathParameters; } public void setPathParameters(Map<String, String> pathParameters) { this.pathParameters = pathParameters; } public Map<String, String> getStageVariables() { return stageVariables; } public void setStageVariables(Map<String, String> stageVariables) { this.stageVariables = stageVariables; } public Map<String, Object> getRequestContext() { return requestContext; } public void setRequestContext(Map<String, Object> requestContext) { this.requestContext = requestContext; } public String getIdentitySource() { return identitySource; } public void setIdentitySource(String identitySource) { this.identitySource = identitySource; } } 4. JWT Authorizer
package com.example.apigateway.authorizer; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; import com.example.apigateway.model.AuthorizerResponse; import com.example.apigateway.model.PolicyDocument; import com.example.apigateway.model.Statement; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.HashMap; import java.util.Map; public class JWTAuthorizer implements Authorizer { private static final Logger log = LoggerFactory.getLogger(JWTAuthorizer.class); private final String jwksUrl; private final String issuer; private final String audience; private final Map<String, RSAPublicKey> publicKeys; private final ObjectMapper objectMapper; public JWTAuthorizer(String jwksUrl, String issuer, String audience) { this.jwksUrl = jwksUrl; this.issuer = issuer; this.audience = audience; this.publicKeys = new HashMap<>(); this.objectMapper = new ObjectMapper(); loadPublicKeys(); } @Override public AuthorizerResponse authorize(AuthorizerRequest request) { if (!(request instanceof TokenAuthorizerRequest)) { throw new IllegalArgumentException("JWTAuthorizer requires TokenAuthorizerRequest"); } TokenAuthorizerRequest tokenRequest = (TokenAuthorizerRequest) request; String token = extractToken(tokenRequest.getAuthorizationToken()); try { DecodedJWT jwt = verifyToken(token); Map<String, String> context = extractContext(jwt); return createAuthorizerResponse(jwt.getSubject(), "Allow", tokenRequest.getMethodArn(), context); } catch (JWTVerificationException e) { log.warn("JWT verification failed: {}", e.getMessage()); return createAuthorizerResponse("unknown", "Deny", tokenRequest.getMethodArn(), null); } catch (Exception e) { log.error("JWT authorization error", e); return createAuthorizerResponse("unknown", "Deny", tokenRequest.getMethodArn(), null); } } private String extractToken(String authorizationToken) { if (authorizationToken == null || !authorizationToken.startsWith("Bearer ")) { throw new JWTVerificationException("Invalid authorization token format"); } return authorizationToken.substring(7); } private DecodedJWT verifyToken(String token) { DecodedJWT jwt = JWT.decode(token); String keyId = jwt.getKeyId(); RSAPublicKey publicKey = publicKeys.get(keyId); if (publicKey == null) { throw new JWTVerificationException("Unknown key ID: " + keyId); } Algorithm algorithm = Algorithm.RSA256(publicKey, null); JWTVerifier verifier = com.auth0.jwt.JWT.require(algorithm) .withIssuer(issuer) .withAudience(audience) .build(); return verifier.verify(token); } private Map<String, String> extractContext(DecodedJWT jwt) { Map<String, String> context = new HashMap<>(); context.put("userId", jwt.getSubject()); context.put("issuer", jwt.getIssuer()); context.put("audience", String.join(",", jwt.getAudience())); // Add custom claims to context Map<String, Object> claims = jwt.getClaims(); for (Map.Entry<String, Object> entry : claims.entrySet()) { if (entry.getValue() != null) { context.put(entry.getKey(), entry.getValue().toString()); } } return context; } private void loadPublicKeys() { // In production, fetch JWKS from the provided URL // This is a simplified implementation try { // For demo purposes, we'll create a mock key loading mechanism // Real implementation would fetch from jwksUrl and parse the JWKS log.info("Loading public keys from: {}", jwksUrl); // Example: Load from environment or configuration String publicKeyStr = System.getenv("JWT_PUBLIC_KEY"); if (publicKeyStr != null) { RSAPublicKey publicKey = loadPublicKey(publicKeyStr); publicKeys.put("default", publicKey); } } catch (Exception e) { log.error("Failed to load public keys", e); } } private RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception { String publicKeyPEM = publicKeyStr .replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "") .replaceAll("\\s", ""); byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); return (RSAPublicKey) keyFactory.generatePublic(keySpec); } private AuthorizerResponse createAuthorizerResponse(String principalId, String effect, String methodArn, Map<String, String> context) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId(principalId); response.setPolicyDocument(createPolicyDocument(effect, methodArn)); response.setContext(context); return response; } private PolicyDocument createPolicyDocument(String effect, String methodArn) { PolicyDocument policyDocument = new PolicyDocument(); policyDocument.setVersion("2012-10-17"); Statement statement = new Statement(); statement.setAction("execute-api:Invoke"); statement.setEffect(effect); statement.setResource(methodArn); policyDocument.setStatement(new Statement[]{statement}); return policyDocument; } } 5. Lambda Token Authorizer
package com.example.apigateway.authorizer; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.example.apigateway.model.AuthorizerResponse; import com.example.apigateway.model.TokenAuthorizerRequest; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class LambdaTokenAuthorizer implements RequestHandler<TokenAuthorizerRequest, AuthorizerResponse> { private static final Logger log = LoggerFactory.getLogger(LambdaTokenAuthorizer.class); private final ObjectMapper objectMapper; private final JWTAuthorizer jwtAuthorizer; private final ApiKeyAuthorizer apiKeyAuthorizer; public LambdaTokenAuthorizer() { this.objectMapper = new ObjectMapper(); this.jwtAuthorizer = new JWTAuthorizer( System.getenv("JWKS_URL"), System.getenv("JWT_ISSUER"), System.getenv("JWT_AUDIENCE") ); this.apiKeyAuthorizer = new ApiKeyAuthorizer(); } @Override public AuthorizerResponse handleRequest(TokenAuthorizerRequest request, Context context) { log.info("Processing token authorizer request for method: {}", request.getMethodArn()); try { String authToken = request.getAuthorizationToken(); // Determine token type and delegate to appropriate authorizer if (authToken.startsWith("Bearer ")) { // JWT Token TokenAuthorizerRequest jwtRequest = new TokenAuthorizerRequest(); jwtRequest.setAuthorizationToken(authToken); jwtRequest.setMethodArn(request.getMethodArn()); jwtRequest.setType(request.getType()); return jwtAuthorizer.authorize(jwtRequest); } else if (authToken.startsWith("ApiKey ")) { // API Key TokenAuthorizerRequest apiKeyRequest = new TokenAuthorizerRequest(); apiKeyRequest.setAuthorizationToken(authToken); apiKeyRequest.setMethodArn(request.getMethodArn()); apiKeyRequest.setType(request.getType()); return apiKeyAuthorizer.authorize(apiKeyRequest); } else { log.warn("Unsupported authorization token type"); return createDenyResponse(request.getMethodArn()); } } catch (Exception e) { log.error("Error in token authorizer", e); return createDenyResponse(request.getMethodArn()); } } private AuthorizerResponse createDenyResponse(String methodArn) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId("unknown"); PolicyDocument policyDocument = new PolicyDocument(); policyDocument.setVersion("2012-10-17"); Statement statement = new Statement(); statement.setAction("execute-api:Invoke"); statement.setEffect("Deny"); statement.setResource(methodArn); policyDocument.setStatement(new Statement[]{statement}); response.setPolicyDocument(policyDocument); return response; } } class ApiKeyAuthorizer implements Authorizer { private static final Logger log = LoggerFactory.getLogger(ApiKeyAuthorizer.class); private final Map<String, String> validApiKeys; public ApiKeyAuthorizer() { this.validApiKeys = new HashMap<>(); // In production, load from secure storage (AWS Secrets Manager, Parameter Store, etc.) validApiKeys.put("valid-key-123", "user-123"); validApiKeys.put("valid-key-456", "user-456"); } @Override public AuthorizerResponse authorize(AuthorizerRequest request) { if (!(request instanceof TokenAuthorizerRequest)) { throw new IllegalArgumentException("ApiKeyAuthorizer requires TokenAuthorizerRequest"); } TokenAuthorizerRequest tokenRequest = (TokenAuthorizerRequest) request; String apiKey = extractApiKey(tokenRequest.getAuthorizationToken()); if (isValidApiKey(apiKey)) { String userId = validApiKeys.get(apiKey); Map<String, String> context = new HashMap<>(); context.put("apiKey", apiKey); context.put("userId", userId); return createAllowResponse(userId, tokenRequest.getMethodArn(), context); } else { log.warn("Invalid API key provided"); return createDenyResponse(tokenRequest.getMethodArn()); } } private String extractApiKey(String authorizationToken) { if (authorizationToken == null || !authorizationToken.startsWith("ApiKey ")) { throw new IllegalArgumentException("Invalid API key format"); } return authorizationToken.substring(7); } private boolean isValidApiKey(String apiKey) { return validApiKeys.containsKey(apiKey); } private AuthorizerResponse createAllowResponse(String principalId, String methodArn, Map<String, String> context) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId(principalId); response.setPolicyDocument(createPolicyDocument("Allow", methodArn)); response.setContext(context); return response; } private AuthorizerResponse createDenyResponse(String methodArn) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId("unknown"); response.setPolicyDocument(createPolicyDocument("Deny", methodArn)); return response; } private PolicyDocument createPolicyDocument(String effect, String methodArn) { PolicyDocument policyDocument = new PolicyDocument(); policyDocument.setVersion("2012-10-17"); Statement statement = new Statement(); statement.setAction("execute-api:Invoke"); statement.setEffect(effect); statement.setResource(methodArn); policyDocument.setStatement(new Statement[]{statement}); return policyDocument; } } 6. Request Authorizer
package com.example.apigateway.authorizer; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.example.apigateway.model.AuthorizerResponse; import com.example.apigateway.model.RequestAuthorizerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class LambdaRequestAuthorizer implements RequestHandler<RequestAuthorizerRequest, AuthorizerResponse> { private static final Logger log = LoggerFactory.getLogger(LambdaRequestAuthorizer.class); private final HeaderAuthorizer headerAuthorizer; private final QueryParamAuthorizer queryParamAuthorizer; private final IPRestrictionAuthorizer ipAuthorizer; public LambdaRequestAuthorizer() { this.headerAuthorizer = new HeaderAuthorizer(); this.queryParamAuthorizer = new QueryParamAuthorizer(); this.ipAuthorizer = new IPRestrictionAuthorizer(); } @Override public AuthorizerResponse handleRequest(RequestAuthorizerRequest request, Context context) { log.info("Processing request authorizer for method: {}", request.getMethodArn()); try { // Extract identity from multiple sources String identity = extractIdentity(request); if (identity == null) { log.warn("No identity found in request"); return createDenyResponse(request.getMethodArn()); } // Validate identity if (!isValidIdentity(identity)) { log.warn("Invalid identity: {}", identity); return createDenyResponse(request.getMethodArn()); } // Check IP restrictions if (!ipAuthorizer.isAllowed(request)) { log.warn("IP address not allowed"); return createDenyResponse(request.getMethodArn()); } // Create context with request details Map<String, String> authContext = createContext(request, identity); return createAllowResponse(identity, request.getMethodArn(), authContext); } catch (Exception e) { log.error("Error in request authorizer", e); return createDenyResponse(request.getMethodArn()); } } private String extractIdentity(RequestAuthorizerRequest request) { // Try headers first String identity = headerAuthorizer.extractIdentity(request); if (identity != null) { return identity; } // Try query parameters identity = queryParamAuthorizer.extractIdentity(request); if (identity != null) { return identity; } return null; } private boolean isValidIdentity(String identity) { // In production, validate against user database or external service return identity != null && !identity.trim().isEmpty(); } private Map<String, String> createContext(RequestAuthorizerRequest request, String identity) { Map<String, String> context = new HashMap<>(); context.put("userId", identity); context.put("sourceIp", extractSourceIp(request)); context.put("userAgent", extractUserAgent(request)); context.put("requestTime", String.valueOf(System.currentTimeMillis())); // Add custom business context context.put("role", "user"); // In production, fetch from user profile context.put("department", "engineering"); // In production, fetch from user profile return context; } private String extractSourceIp(RequestAuthorizerRequest request) { if (request.getRequestContext() != null) { Map<String, Object> identity = (Map<String, Object>) request.getRequestContext().get("identity"); if (identity != null) { return (String) identity.get("sourceIp"); } } return "unknown"; } private String extractUserAgent(RequestAuthorizerRequest request) { if (request.getHeaders() != null) { return request.getHeaders().get("User-Agent"); } return "unknown"; } private AuthorizerResponse createAllowResponse(String principalId, String methodArn, Map<String, String> context) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId(principalId); response.setPolicyDocument(createPolicyDocument("Allow", methodArn)); response.setContext(context); return response; } private AuthorizerResponse createDenyResponse(String methodArn) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId("unknown"); response.setPolicyDocument(createPolicyDocument("Deny", methodArn)); return response; } private PolicyDocument createPolicyDocument(String effect, String methodArn) { PolicyDocument policyDocument = new PolicyDocument(); policyDocument.setVersion("2012-10-17"); Statement statement = new Statement(); statement.setAction("execute-api:Invoke"); statement.setEffect(effect); statement.setResource(methodArn); policyDocument.setStatement(new Statement[]{statement}); return policyDocument; } } class HeaderAuthorizer { private static final Logger log = LoggerFactory.getLogger(HeaderAuthorizer.class); public String extractIdentity(RequestAuthorizerRequest request) { Map<String, String> headers = request.getHeaders(); if (headers == null) { return null; } // Check for API key in header String apiKey = headers.get("X-API-Key"); if (apiKey != null && isValidApiKey(apiKey)) { return "api-key-" + apiKey; } // Check for custom auth header String authHeader = headers.get("Authorization"); if (authHeader != null && authHeader.startsWith("Custom ")) { return extractCustomIdentity(authHeader); } return null; } private boolean isValidApiKey(String apiKey) { // In production, validate against database or external service return apiKey != null && apiKey.startsWith("valid-"); } private String extractCustomIdentity(String authHeader) { try { String token = authHeader.substring(7); // Decode or validate custom token return "custom-" + token; } catch (Exception e) { log.warn("Failed to extract custom identity", e); return null; } } } class QueryParamAuthorizer { private static final Logger log = LoggerFactory.getLogger(QueryParamAuthorizer.class); public String extractIdentity(RequestAuthorizerRequest request) { Map<String, String> queryParams = request.getQueryStringParameters(); if (queryParams == null) { return null; } // Check for API key in query parameters String apiKey = queryParams.get("api_key"); if (apiKey != null && isValidApiKey(apiKey)) { return "api-key-" + apiKey; } // Check for token in query parameters String token = queryParams.get("token"); if (token != null && isValidToken(token)) { return "token-" + token; } return null; } private boolean isValidApiKey(String apiKey) { // In production, validate against database or external service return apiKey != null && apiKey.startsWith("valid-"); } private boolean isValidToken(String token) { // In production, validate token return token != null && token.length() > 10; } } class IPRestrictionAuthorizer { private static final Logger log = LoggerFactory.getLogger(IPRestrictionAuthorizer.class); private final List<String> allowedRanges = Arrays.asList("192.168.1.0/24", "10.0.0.0/8"); public boolean isAllowed(RequestAuthorizerRequest request) { String sourceIp = extractSourceIp(request); if (sourceIp == null || "unknown".equals(sourceIp)) { log.warn("Could not determine source IP"); return false; } return isIpInRange(sourceIp, allowedRanges); } private String extractSourceIp(RequestAuthorizerRequest request) { if (request.getRequestContext() != null) { Map<String, Object> identity = (Map<String, Object>) request.getRequestContext().get("identity"); if (identity != null) { return (String) identity.get("sourceIp"); } } return null; } private boolean isIpInRange(String ip, List<String> ranges) { // Simplified IP range check - in production, use proper IP address validation for (String range : ranges) { if (ip.startsWith(range.replace("/24", "").replace("/8", ""))) { return true; } } return false; } } 7. Cognito Authorizer Integration
package com.example.apigateway.authorizer; import com.example.apigateway.model.AuthorizerResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class CognitoAuthorizer implements Authorizer { private static final Logger log = LoggerFactory.getLogger(CognitoAuthorizer.class); private final String userPoolId; private final String appClientId; public CognitoAuthorizer(String userPoolId, String appClientId) { this.userPoolId = userPoolId; this.appClientId = appClientId; } @Override public AuthorizerResponse authorize(AuthorizerRequest request) { if (!(request instanceof TokenAuthorizerRequest)) { throw new IllegalArgumentException("CognitoAuthorizer requires TokenAuthorizerRequest"); } TokenAuthorizerRequest tokenRequest = (TokenAuthorizerRequest) request; String token = extractToken(tokenRequest.getAuthorizationToken()); try { CognitoUser user = validateCognitoToken(token); Map<String, String> context = createUserContext(user); return createAuthorizerResponse(user.getUsername(), "Allow", tokenRequest.getMethodArn(), context); } catch (Exception e) { log.warn("Cognito token validation failed: {}", e.getMessage()); return createAuthorizerResponse("unknown", "Deny", tokenRequest.getMethodArn(), null); } } private String extractToken(String authorizationToken) { if (authorizationToken == null || !authorizationToken.startsWith("Bearer ")) { throw new IllegalArgumentException("Invalid authorization token format"); } return authorizationToken.substring(7); } private CognitoUser validateCognitoToken(String token) { // In production, use AWS Cognito SDK to validate the token // This is a simplified implementation // For real implementation, you would: // 1. Download Cognito JWKS // 2. Verify JWT signature // 3. Validate claims (issuer, audience, expiration) // 4. Check token use log.info("Validating Cognito token for user pool: {}", userPoolId); // Mock validation - replace with actual Cognito validation if (!isValidCognitoToken(token)) { throw new RuntimeException("Invalid Cognito token"); } return extractUserFromToken(token); } private boolean isValidCognitoToken(String token) { // Simplified validation - in production, use proper JWT validation // against Cognito user pool return token != null && token.length() > 50; } private CognitoUser extractUserFromToken(String token) { // In production, decode JWT and extract claims // This is a mock implementation CognitoUser user = new CognitoUser(); user.setUsername("cognito-user-123"); user.setEmail("[email protected]"); user.setGroups(new String[]{"users", "developers"}); user.setCustomAttributes(new HashMap<>()); user.getCustomAttributes().put("department", "engineering"); user.getCustomAttributes().put("role", "developer"); return user; } private Map<String, String> createUserContext(CognitoUser user) { Map<String, String> context = new HashMap<>(); context.put("username", user.getUsername()); context.put("email", user.getEmail()); context.put("groups", String.join(",", user.getGroups())); // Add custom attributes to context if (user.getCustomAttributes() != null) { user.getCustomAttributes().forEach((key, value) -> { context.put(key, value); }); } return context; } private AuthorizerResponse createAuthorizerResponse(String principalId, String effect, String methodArn, Map<String, String> context) { AuthorizerResponse response = new AuthorizerResponse(); response.setPrincipalId(principalId); response.setPolicyDocument(createPolicyDocument(effect, methodArn)); response.setContext(context); return response; } private PolicyDocument createPolicyDocument(String effect, String methodArn) { PolicyDocument policyDocument = new PolicyDocument(); policyDocument.setVersion("2012-10-17"); Statement statement = new Statement(); statement.setAction("execute-api:Invoke"); statement.setEffect(effect); statement.setResource(methodArn); policyDocument.setStatement(new Statement[]{statement}); return policyDocument; } } class CognitoUser { private String username; private String email; private String[] groups; private Map<String, String> customAttributes; // Getters and Setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String[] getGroups() { return groups; } public void setGroups(String[] groups) { this.groups = groups; } public Map<String, String> getCustomAttributes() { return customAttributes; } public void setCustomAttributes(Map<String, String> customAttributes) { this.customAttributes = customAttributes; } } 8. Authorizer Factory and Configuration
package com.example.apigateway.factory; import com.example.apigateway.authorizer.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; public class AuthorizerFactory { private static final Logger log = LoggerFactory.getLogger(AuthorizerFactory.class); private static final Map<String, Authorizer> authorizers = new HashMap<>(); static { // Initialize built-in authorizers initializeAuthorizers(); } public static Authorizer getAuthorizer(String type, Map<String, String> config) { String key = type + "-" + config.hashCode(); return authorizers.computeIfAbsent(key, k -> createAuthorizer(type, config)); } private static Authorizer createAuthorizer(String type, Map<String, String> config) { switch (type.toLowerCase()) { case "jwt": return createJWTAuthorizer(config); case "cognito": return createCognitoAuthorizer(config); case "apikey": return new ApiKeyAuthorizer(); case "custom": return createCustomAuthorizer(config); default: throw new IllegalArgumentException("Unknown authorizer type: " + type); } } private static JWTAuthorizer createJWTAuthorizer(Map<String, String> config) { String jwksUrl = config.get("jwksUrl"); String issuer = config.get("issuer"); String audience = config.get("audience"); if (jwksUrl == null || issuer == null || audience == null) { throw new IllegalArgumentException("JWT authorizer requires jwksUrl, issuer, and audience configuration"); } return new JWTAuthorizer(jwksUrl, issuer, audience); } private static CognitoAuthorizer createCognitoAuthorizer(Map<String, String> config) { String userPoolId = config.get("userPoolId"); String appClientId = config.get("appClientId"); if (userPoolId == null || appClientId == null) { throw new IllegalArgumentException("Cognito authorizer requires userPoolId and appClientId configuration"); } return new CognitoAuthorizer(userPoolId, appClientId); } private static Authorizer createCustomAuthorizer(Map<String, String> config) { String className = config.get("className"); if (className == null) { throw new IllegalArgumentException("Custom authorizer requires className configuration"); } try { Class<?> authorizerClass = Class.forName(className); return (Authorizer) authorizerClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to create custom authorizer: " + className, e); } } private static void initializeAuthorizers() { // Pre-configure common authorizers Map<String, String> jwtConfig = new HashMap<>(); jwtConfig.put("jwksUrl", "https://cognito-idp.region.amazonaws.com/userPoolId/.well-known/jwks.json"); jwtConfig.put("issuer", "https://cognito-idp.region.amazonaws.com/userPoolId"); jwtConfig.put("audience", "appClientId"); authorizers.put("jwt-default", new JWTAuthorizer( jwtConfig.get("jwksUrl"), jwtConfig.get("issuer"), jwtConfig.get("audience") )); log.info("Authorizer factory initialized with {} pre-configured authorizers", authorizers.size()); } } 9. Example Usage and Deployment
package com.example.apigateway.demo; import com.amazonaws.services.lambda.runtime.Context; import com.example.apigateway.authorizer.LambdaTokenAuthorizer; import com.example.apigateway.model.TokenAuthorizerRequest; import com.example.apigateway.model.AuthorizerResponse; public class AuthorizerDemo { public static void main(String[] args) { // Example token authorizer usage LambdaTokenAuthorizer authorizer = new LambdaTokenAuthorizer(); TokenAuthorizerRequest request = new TokenAuthorizerRequest(); request.setType("TOKEN"); request.setAuthorizationToken("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."); request.setMethodArn("arn:aws:execute-api:us-east-1:123456789012:api123/prod/GET/users"); AuthorizerResponse response = authorizer.handleRequest(request, null); System.out.println("Authorization Result:"); System.out.println("Principal: " + response.getPrincipalId()); System.out.println("Effect: " + response.getPolicyDocument().getStatement()[0].getEffect()); System.out.println("Context: " + response.getContext()); } } // Serverless Framework configuration example (serverless.yml) /* service: api-gateway-authorizers provider: name: aws runtime: java11 region: us-east-1 functions: tokenAuthorizer: handler: com.example.apigateway.authorizer.LambdaTokenAuthorizer::handleRequest environment: JWKS_URL: ${env:JWKS_URL} JWT_ISSUER: ${env:JWT_ISSUER} JWT_AUDIENCE: ${env:JWT_AUDIENCE} requestAuthorizer: handler: com.example.apigateway.authorizer.LambdaRequestAuthorizer::handleRequest resources: Resources: ApiGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: Name: SecureAPI TokenAuthorizerPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt TokenAuthorizerLambdaFunction.Arn Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com */ // CloudFormation template example /* AWSTemplateFormatVersion: '2010-09-09' Resources: ApiGatewayAuthorizer: Type: AWS::ApiGateway::Authorizer Properties: Name: JWTAuthorizer RestApiId: !Ref ApiGateway Type: TOKEN IdentitySource: method.request.header.Authorization AuthorizerUri: !Sub - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerFunction.Arn}/invocations" - AuthorizerFunction: !Ref TokenAuthorizerFunction AuthorizerResultTtlInSeconds: 300 */ // Terraform configuration example /* resource "aws_api_gateway_authorizer" "jwt_authorizer" { name = "jwt-authorizer" rest_api_id = aws_api_gateway_rest_api.main.id authorizer_uri = aws_lambda_function.authorizer.invoke_arn authorizer_credentials = aws_iam_role.api_gateway.arn identity_source = "method.request.header.Authorization" type = "REQUEST" } resource "aws_lambda_permission" "api_gateway" { statement_id = "AllowExecutionFromAPIGateway" action = "lambda:InvokeFunction" function_name = aws_lambda_function.authorizer.function_name principal = "apigateway.amazonaws.com" source_arn = "${aws_api_gateway_rest_api.main.execution_arn}/*/*" } */ Key Features
- Multiple Authorizer Types: JWT, Lambda Token, Request, Cognito, API Key
- AWS Lambda Integration: Ready for serverless deployment
- Flexible Identity Sources: Headers, query parameters, tokens
- Context Propagation: Rich context for downstream services
- Policy Generation: Dynamic IAM policy creation
- Security Best Practices: Token validation, IP restrictions, rate limiting
- Extensible Architecture: Easy to add custom authorizers
- Production Ready: Error handling, logging, configuration management
This implementation provides a complete API Gateway Authorizer solution in Java that can be deployed to AWS Lambda and integrated with API Gateway for securing your APIs.