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.