This article explains using our OSS lambda-utilities to configure a spring cloud function java lambda to allow method level authorisation using API Gateway and Cognito.
See our OSS repository here.
Architecture Overview
The security setup integrates three key AWS services:
- AWS Cognito – Identity provider and JWT issuer
- AWS API Gateway – HTTP API with JWT authorizer
- AWS Lambda – Function execution environment
Key Components
1. ApiGatewayResponseDecoratorFactory
This is the central factory that creates decorated Spring Cloud Functions with security and error handling:
@Service
public class ApiGatewayResponseDecoratorFactory {
// Creates decorated functions that handle security, errors, and responses
public <Input, Output> Function<Input, APIGatewayV2HTTPResponse> create(Function<Input, Output> function)
}
Purpose:
- Wraps your business logic functions
- Automatically handles authentication extraction from API Gateway events
- Converts exceptions to proper HTTP responses
- Manages Spring Security context
2. Security Configuration Setup
The security is configured through : AwsCloudFunctionSpringSecurityConfiguration
@EnableMethodSecurity
@Configuration
@Import({LimeJacksonJsonConfiguration.class, ApiGatewayResponseDecoratorFactory.class})
@ComponentScan(basePackageClasses = ApiGatewayAuthenticationMapper.class)
public class AwsCloudFunctionSpringSecurityConfiguration
This enables:
- Method-level security (
@PreAuthorize
,@Secured
, etc.) - Automatic authentication mapping
- Exception handling for security violations
3. Authentication Flow
The authentication process works as follows:
- API Gateway receives request with JWT token in Authorization header
- JWT Authorizer validates the token against Cognito
- API Gateway forwards the validated JWT claims in the request context
- extracts authentication from the event:
- Reads JWT claims from request context
- Creates object
ApiGatewayAuthentication
- Maps Cognito groups to Spring Security authorities
- Spring Security context is populated for method-level security
AWS Infrastructure Setup
API Gateway Configuration
# Example CDK/CloudFormation for HTTP API with JWT Authorizer
HttpApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: MySecureApi
ProtocolType: HTTP
JwtAuthorizer:
Type: AWS::ApiGatewayV2::Authorizer
Properties:
ApiId: !Ref HttpApi
AuthorizerType: JWT
IdentitySource:
- $request.header.Authorization
JwtConfiguration:
Audience:
- your-cognito-client-id
Issuer: https://cognito-idp.{region}.amazonaws.com/{user-pool-id}
Route:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref HttpApi
RouteKey: POST /secure-endpoint
Target: !Sub integrations/${LambdaIntegration}
AuthorizerId: !Ref JwtAuthorizer
AuthorizationType: JWT
Cognito Configuration
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: MyAppUsers
Schema:
- Name: email
AttributeDataType: String
Required: true
Policies:
PasswordPolicy:
MinimumLength: 8
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
ClientName: MyAppClient
GenerateSecret: false
ExplicitAuthFlows:
- ADMIN_NO_SRP_AUTH
- USER_PASSWORD_AUTH
Implementation Example
1. Create Your Business Function
@Component
public class SecureBusinessLogic {
public String processSecureData(MyRequest request) {
// Your business logic here
return "Processed: " + request.getData();
}
}
2. Create the Lambda Handler
@Configuration
@Import(LimeAwsLambdaConfiguration.class)
public class LambdaConfiguration {
@Autowired
private ApiGatewayResponseDecoratorFactory decoratorFactory;
@Autowired
private SecureBusinessLogic businessLogic;
@Bean
public Function<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> secureFunction() {
return decoratorFactory.create(event -> {
// Extract request body
MyRequest request = parseRequest(event.getBody());
// Business logic with automatic security context
return businessLogic.processSecureData(request);
});
}
}
3. Add Method-Level Security
@Component
public class SecureBusinessLogic {
@PreAuthorize("hasAuthority('ADMIN')")
public String processAdminData(MyRequest request) {
return "Admin processed: " + request.getData();
}
@PreAuthorize("hasAuthority('USER') or hasAuthority('ADMIN')")
public String processUserData(MyRequest request) {
return "User processed: " + request.getData();
}
}
4. Access Current User Context
@Bean
public Function<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> contextAwareFunction() {
return decoratorFactory.create(event -> {
// Access current authentication
ApiGatewayContext context = decoratorFactory.getCurrentApiGatewayContext();
ApiGatewayAuthentication auth = context.getAuthentication();
if (auth.isAuthenticated()) {
String username = auth.getPrincipal().getName();
Set<String> groups = auth.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
return new UserResponse(username, groups, "Success");
} else {
return new UserResponse("anonymous", Set.of("ANONYMOUS"), "Limited access");
}
});
}
Configuration Properties
The authentication mapper supports several configuration properties:
com:
limemojito:
aws:
lambda:
security:
claimsKey: "cognito:groups" # Cognito groups claim
anonymous:
sub: "ANONYMOUS"
userName: "anonymous"
authority: "ANONYMOUS"
Security Benefits
- Automatic JWT Validation: API Gateway validates tokens before reaching Lambda
- Claims Extraction: Automatic mapping of Cognito user groups to Spring authorities
- Method Security: Use standard Spring Security annotations
- Exception Handling: Automatic conversion of security exceptions to HTTP responses
- Context Access: Easy access to user information and claims
- Anonymous Support: Graceful handling of unauthenticated requests
Error Handling
The decorator automatically handles:
- Authentication failures → 401 Unauthorized
- Authorization failures → 403 Forbidden
- Validation errors → 400 Bad Request
- General exceptions → 500 Internal Server Error
This architecture provides a robust, scalable security solution that leverages AWS managed services while maintaining clean separation of concerns in your Spring Cloud Function implementation.