From 004438a237a271ed2b71bcb7d619b5449eaa4c26 Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 2 May 2017 10:18:44 -0700 Subject: [PATCH 1/3] Switched from json annotations to custom deserializer --- .../model/CognitoAuthorizerClaims.java | 36 +++++++++----- .../CognitoAuthorizerClaimsDeserializer.java | 47 +++++++++++++++++++ 2 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index 552644df6..f29dc671a 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -13,9 +13,9 @@ package com.amazonaws.serverless.proxy.internal.model; -import com.fasterxml.jackson.annotation.JsonProperty; +import com.amazonaws.serverless.proxy.internal.serialization.CognitoAuthorizerClaimsDeserializer; -import java.time.format.DateTimeFormatter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; /** @@ -36,31 +36,25 @@ * } * */ +@JsonDeserialize(using = CognitoAuthorizerClaimsDeserializer.class) public class CognitoAuthorizerClaims { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- - @JsonProperty(value = "sub") private String subject; - @JsonProperty(value = "aud") private String audience; - @JsonProperty(value = "iss") private String issuer; - @JsonProperty(value = "token_use") private String tokenUse; - @JsonProperty(value = "cognito:username") private String username; private String email; - @JsonProperty(value = "email_verified") private boolean emailVerified; - @JsonProperty(value = "auth_time") private Long authTime; - @JsonProperty(value = "exp") private String expiration; - @JsonProperty(value = "iat") private String issuedAt; + private String phoneNumber; + private boolean phoneNumberVerified; //------------------------------------------------------------- @@ -163,4 +157,24 @@ public String getIssuedAt() { public void setIssuedAt(String issuedAt) { this.issuedAt = issuedAt; } + + + public String getPhoneNumber() { + return phoneNumber; + } + + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + + public boolean isPhoneNumberVerified() { + return phoneNumberVerified; + } + + + public void setPhoneNumberVerified(boolean phoneNumberVerified) { + this.phoneNumberVerified = phoneNumberVerified; + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java new file mode 100644 index 000000000..b7a9992cd --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java @@ -0,0 +1,47 @@ +package com.amazonaws.serverless.proxy.internal.serialization; + + +import com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; + + +/** + * Created by bulianis on 5/2/17. + */ +public class CognitoAuthorizerClaimsDeserializer extends JsonDeserializer { + @Override + public CognitoAuthorizerClaims deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException, JsonProcessingException { + CognitoAuthorizerClaims output = new CognitoAuthorizerClaims(); + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + + output.setSubject(node.get("sub").asText()); + output.setUsername(node.get("cognito:username").asText()); + output.setIssuer(node.get("iss").asText()); + output.setAuthTime(node.get("auth_time").asLong()); + output.setAudience(node.get("aud").asText()); + output.setExpiration(node.get("exp").asText()); + output.setTokenUse(node.get("token_use").asText()); + output.setIssuedAt(node.get("iat").asText()); + + if (node.get("email") != null) { + output.setEmailVerified(node.get("email_verified").asBoolean()); + output.setEmail(node.get("email").asText()); + } + + if (node.get("phone_number") != null) { + output.setPhoneNumber(node.get("phone_number").asText()); + output.setPhoneNumberVerified(node.get("phone_number_verified").asBoolean()); + } + + return output; + } +} From 898a1b8d277042e7a22aeba8df432e7800643754 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 3 May 2017 08:17:14 -0700 Subject: [PATCH 2/3] Revert "Switched from json annotations to custom deserializer" This reverts commit 004438a237a271ed2b71bcb7d619b5449eaa4c26. --- .../model/CognitoAuthorizerClaims.java | 36 +++++--------- .../CognitoAuthorizerClaimsDeserializer.java | 47 ------------------- 2 files changed, 11 insertions(+), 72 deletions(-) delete mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index f29dc671a..552644df6 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -13,9 +13,9 @@ package com.amazonaws.serverless.proxy.internal.model; -import com.amazonaws.serverless.proxy.internal.serialization.CognitoAuthorizerClaimsDeserializer; +import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.format.DateTimeFormatter; /** @@ -36,25 +36,31 @@ * } * */ -@JsonDeserialize(using = CognitoAuthorizerClaimsDeserializer.class) public class CognitoAuthorizerClaims { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- + @JsonProperty(value = "sub") private String subject; + @JsonProperty(value = "aud") private String audience; + @JsonProperty(value = "iss") private String issuer; + @JsonProperty(value = "token_use") private String tokenUse; + @JsonProperty(value = "cognito:username") private String username; private String email; + @JsonProperty(value = "email_verified") private boolean emailVerified; + @JsonProperty(value = "auth_time") private Long authTime; + @JsonProperty(value = "exp") private String expiration; + @JsonProperty(value = "iat") private String issuedAt; - private String phoneNumber; - private boolean phoneNumberVerified; //------------------------------------------------------------- @@ -157,24 +163,4 @@ public String getIssuedAt() { public void setIssuedAt(String issuedAt) { this.issuedAt = issuedAt; } - - - public String getPhoneNumber() { - return phoneNumber; - } - - - public void setPhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; - } - - - public boolean isPhoneNumberVerified() { - return phoneNumberVerified; - } - - - public void setPhoneNumberVerified(boolean phoneNumberVerified) { - this.phoneNumberVerified = phoneNumberVerified; - } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java deleted file mode 100644 index b7a9992cd..000000000 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/serialization/CognitoAuthorizerClaimsDeserializer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.amazonaws.serverless.proxy.internal.serialization; - - -import com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - -import java.io.IOException; - - -/** - * Created by bulianis on 5/2/17. - */ -public class CognitoAuthorizerClaimsDeserializer extends JsonDeserializer { - @Override - public CognitoAuthorizerClaims deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException, JsonProcessingException { - CognitoAuthorizerClaims output = new CognitoAuthorizerClaims(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - output.setSubject(node.get("sub").asText()); - output.setUsername(node.get("cognito:username").asText()); - output.setIssuer(node.get("iss").asText()); - output.setAuthTime(node.get("auth_time").asLong()); - output.setAudience(node.get("aud").asText()); - output.setExpiration(node.get("exp").asText()); - output.setTokenUse(node.get("token_use").asText()); - output.setIssuedAt(node.get("iat").asText()); - - if (node.get("email") != null) { - output.setEmailVerified(node.get("email_verified").asBoolean()); - output.setEmail(node.get("email").asText()); - } - - if (node.get("phone_number") != null) { - output.setPhoneNumber(node.get("phone_number").asText()); - output.setPhoneNumberVerified(node.get("phone_number_verified").asBoolean()); - } - - return output; - } -} From 09c848fb9fc9c25786fdcd79f7da4ac3c213933b Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 3 May 2017 10:18:03 -0700 Subject: [PATCH 3/3] Reverted to original annotations and updated README --- README.md | 37 ++++++++++++++- .../model/ApiGatewayAuthorizerContext.java | 1 - .../sample/spring/StreamLambdaHandler.java | 45 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java diff --git a/README.md b/README.md index 73315719e..6c088e71a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ To include the library in your Maven project, add the desired implementation to The simplest way to run your application serverlessly is to configure [API Gateway](https://aws.amazon.com/api-gateway/) to use the [`AWS_PROXY`](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource) integration type and configure your desired `LambdaContainerHandler` implementation to use `AwsProxyRequest`/`AwsProxyResponse` readers and writers. Both Spark and Jersey implementations provide static helper methods that -pre-configure this for you. +pre-configure this for you. + +When using a Cognito User Pool authorizer, use the Lambda `RequestStreamHandler` instead of the POJO-based `RequestHandler` handler. An example of this is included at the bottom of this file. The POJO handler does not support Jackson annotations required for the `CognitoAuthorizerClaims` class. ### Jersey support The library expects to receive a valid [JAX-RS](https://jax-rs-spec.java.net) application object. For the Jersey implementation this is the `ResourceConfig` object. @@ -153,3 +155,36 @@ handler.onStartup(c -> { // servlet name mappings are disabled and will throw an exception }); ``` + +# Using the Lambda Stream handler +By default, Lambda does not use Jackson annotations when marshalling and unmarhsalling JSON. This can cause issues when receiving requests that include the claims object from a Cognito User Pool authorizer. To support these type of requests, use Lambda's `RequestStreamHandler` interface instead of the POJO-based `RequestHandler`. This allows you to use a custom version of Jackson with support for annotations. + +This library uses Jackson annotations in the `com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims` object. The example below shows how to do this with a `SpringLambdaContainerHandler`, you can use the same methodology with all of the other implementations. + +```java +public class StreamLambdaHandler implements RequestStreamHandler { + private SpringLambdaContainerHandler handler; + private static ObjectMapper mapper = new ObjectMapper(); + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException { + if (handler == null) { + try { + handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + outputStream.close(); + } + } + + AwsProxyRequest request = mapper.readValue(inputStream, AwsProxyRequest.class); + + AwsProxyResponse resp = handler.proxy(request, context); + + mapper.writeValue(outputStream, resp); + // just in case it wasn't closed by the mapper + outputStream.close(); + } +} +``` \ No newline at end of file diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java index cb51feef6..9cf189a76 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java @@ -39,7 +39,6 @@ public class ApiGatewayAuthorizerContext { private String principalId; private CognitoAuthorizerClaims claims; - //------------------------------------------------------------- // Methods - Public //------------------------------------------------------------- diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java new file mode 100644 index 000000000..c4bafd058 --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java @@ -0,0 +1,45 @@ +package com.amazonaws.serverless.sample.spring; + + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * Created by bulianis on 5/2/17. + */ +public class StreamLambdaHandler implements RequestStreamHandler { + private SpringLambdaContainerHandler handler; + private static ObjectMapper mapper = new ObjectMapper(); + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException { + if (handler == null) { + try { + handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + outputStream.close(); + } + } + + AwsProxyRequest request = mapper.readValue(inputStream, AwsProxyRequest.class); + + AwsProxyResponse resp = handler.proxy(request, context); + + mapper.writeValue(outputStream, resp); + // just in case it wasn't closed by the mapper + outputStream.close(); + } +}