DynamoDBMapper batchDelete method not working in java - java

Hello I am using the following dependency for reading/writing to DynamoDB in spring boot. Internally this api calls DynamoDBMapper for performing any of its operations. DynamoDBMapper is apart of the AWS SDK. I BELIEVE THERE IS AN ISSUE WITH THE DYNAMODBMAPPER BATCH DELETE CALL IN AWS SDK
<dependency>
<groupId>com.github.derjust</groupId>
<artifactId>spring-data-dynamodb</artifactId>
<version>5.1.0</version>
</dependency>
However, when I invoke the DynamoDBMapper batchDelete method I get the following error
java.lang.reflect.InaccessibleObjectException: Unable to make final void java.lang.Throwable.setCause(java.lang.Throwable) accessible: module java.base does not "opens java.lang" to unnamed module #1b083826
Here is what the invoking code looks like
List<Matchup> matchups = (List<Matchup>) matchupRepository.findAll();
DynamoDBMapper dynamoDBMapper = new DynamoDBMapper(amazonDynamoDBClient);
try {
dynamoDBMapper.batchDelete(matchups);
}
catch(Exception e) {
log.error(e.toString());
}
The entity class looks like so
#Data
#NoArgsConstructor
#AllArgsConstructor
#DynamoDBTable(tableName = "Sportsbook")
public class Matchup {
#DynamoDBAttribute(attributeName = "MatchupId")
private String matchupId;
#DynamoDBAttribute(attributeName = "SportsKey")
private String sportsKey;
#DynamoDBAttribute(attributeName = "CommenceTime")
private String commenceTime;
#DynamoDBAttribute(attributeName = "HomeTeam")
private String homeTeam;
#DynamoDBAttribute(attributeName = "AwayTeam")
private String awayTeam;
#DynamoDBHashKey(attributeName = "PK")
public String getPK() {
return "MATCHUP#"+ matchupId;
}
public void setPK(String pk) {
//do nothing
}
}
I noticed all other methods of DynamoDBMapper work except batchDelete. It has the following method signature
List<FailedBatch> batchDelete(Iterable<? extends Object> var1);

You are NOT using the most recent AWS Java API to perform this task. The DynamoDBMapper is part of AWS SDK for Java V1 and no longer considered best practice. Here is the DynamoDBMapper that is part of the V1 DEV Guide https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/examples-dynamodb-items.html
Amazon States:
The AWS SDK for Java 2.x is a major rewrite of the version 1.x code base. It’s built on top of Java 8+ and adds several frequently requested features. These include support for non-blocking I/O and the ability to plug in a different HTTP implementation at run time.
To perform this use case, upgrade from AWS SDK for Java V1 to V2 and use and the Enhanced Client as explained in the AWS Java V2 DEV Guide:
https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/dynamodb-enhanced-client.html
Here is a code example that shows HOW TO Delete batch items using the Enhanced Client. First - assume you have a Customer table with this data and you want to delete batch records.
Here is the full code example with the Enhanced Client.
package com.example.dynamodb;
// snippet-start:[dynamodb.java2.mapping.batchdelete.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.BatchWriteItemEnhancedRequest;
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
// snippet-end:[dynamodb.java2.mapping.batchdelete.import]
/*
* Before running this code example, create an Amazon DynamoDB table named Customer with these columns:
* - id - the id of the record that is the key
* - custName - the customer name
* - email - the email value
* - registrationDate - an instant value when the item was added to the table
*
* Also, ensure that you have set up your development environment, including your credentials.
*
* For information, see this documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class EnhancedBatchDeleteItems {
public static void main(String[] args) {
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create();
Region region = Region.US_EAST_1;
DynamoDbClient ddb = DynamoDbClient.builder()
.region(region)
.credentialsProvider(credentialsProvider)
.build();
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(ddb)
.build();
deleteBatchRecords(enhancedClient);
ddb.close();
}
// snippet-start:[dynamodb.java2.mapping.batchdelete.main]
public static void deleteBatchRecords(DynamoDbEnhancedClient enhancedClient) {
try {
DynamoDbTable<Customer> mappedTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
Key key1 = Key.builder()
.partitionValue("id110")
.build();
Key key2 = Key.builder()
.partitionValue("id120")
.build();
BatchWriteItemEnhancedRequest request = BatchWriteItemEnhancedRequest.builder()
.writeBatches(WriteBatch.builder(Customer.class)
.mappedTableResource(mappedTable)
.addDeleteItem(DeleteItemEnhancedRequest.builder()
.key(key1)
.build())
.build(),
WriteBatch.builder(Customer.class)
.mappedTableResource(mappedTable)
.addDeleteItem(DeleteItemEnhancedRequest.builder()
.key(key2)
.build())
.build())
.build();
// Delete these two items from the table.
enhancedClient.batchWriteItem(request);
System.out.println("Records deleted");
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
// snippet-end:[dynamodb.java2.mapping.batchdelete.main]
}
More information here:
https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem-software.amazon.awssdk.enhanced.dynamodb.model.BatchWriteItemEnhancedRequest-
Now we are left with these items:
Finally, you can find the Customer class, the POM file, etc in AWS Java V2 Github here.

Related

DynamoDB Table Partition key and sort key are 1:1 - how to go about querying only using partition key?

I am attempting to query a table that has a partition key and sort key (however the partition key and sort key are 1:1 and I want to query only using the partition key [in which only one item would be returned]).
QueryRequest query = new QueryRequest()
.withTableName(TABLE_NAME)
.withKeyConditionExpression("testId = :" + "1234567890");
QueryResult result = client.query(query);
This is the code I tried but it did not work (testId is the partition key name and 1234567890 is the partition key value in String form); do y'all know of a method I could use to query by only using the partition key keeping in mind that only one item will be returned since the partition key and sort key are 1:1? Thank you so much in advance. [This is my first Stack Overflow post - my apologies if I worded things poorly, I'm happy to answer any questions about my wording]
FYI: this is the error statement I got when trying to use the code above:
errorMessage": "Invalid KeyConditionExpression: An expression attribute value used in expression is not defined
You should really update to use AWS SDK For Java V2 (Using AWS SDK for V1 is not best practice). Using AWS SDK for Java v2 is best practice for using Amazon DynamoDB API.
To learn more about the AWS V2 Java API, read the Developer Guide here.
Developer guide - AWS SDK for Java 2.x
Now I will answer this question with V2. The solution that worked for me was create a secondary index named year-index. This uses just my partition key named year (and does not use the sort key).
I can successfully query using this index, as shown here that uses the AWS Management Console.
Now only movies with the year 2014 are returned. That is how you query when your table has a composite key made up of a partition key and sort key and you only want to query on partition key.
By the way - you said you have a secondary index. A table can have more then 1 secondary index
Code that you need for V2 to query a secondary Index
I will show you how to use V2 to search for secondary index using three ways.
First way - Use the V2 Enhanced Client
Once you create the secondary index, you can use it to query. As mentioned, I created a secondary index named year-index. I can use this secondary index to query data by using the DynamoDB Enhanced Client.
Because, I am querying the Movies table, I have to create a Class named Movies like this. Notice the use of the #DynamoDbSecondaryPartitionKey annotation.
package com.example.dynamodb;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
#DynamoDbBean
public class Movies {
private String title;
private int year;
private String info;
#DynamoDbSecondaryPartitionKey(indexNames = { "year-index" })
#DynamoDbPartitionKey
public int getYear() {
return this.year;
}
public void setYear(int year) {
this.year = year;
}
#DynamoDbSortKey
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getInfo() {
return this.info;
}
public void setInfo(String info) {
this.info = info;
}
}
Finally, here is the V2 code that lets you query using the secondary index.
package com.example.dynamodb;
// snippet-start:[dynamodb.java2.get_item_index.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbIndex;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.Key;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import java.util.List;
// snippet-end:[dynamodb.java2.get_item_index.import]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
*
* For more information, see the following documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*
* To get an item from an Amazon DynamoDB table using the AWS SDK for Java V2, its better practice to use the
* Enhanced Client, see the EnhancedGetItem example.
*
* Create the Movies table by running the Scenario example and loading the Movies data from the JSON file. Next create a secondary
* index for the Movies table that uses only the year column. Name the index **year-index**. For more information, see:
*
* https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html
*/
public class EnhancedGetItemUsingIndex {
public static void main(String[] args) {
String tableName = "Movies" ; //args[0];
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create();
Region region = Region.US_EAST_1;
DynamoDbClient ddb = DynamoDbClient.builder()
.credentialsProvider(credentialsProvider)
.region(region)
.build();
queryIndex(ddb, tableName);
ddb.close();
}
// snippet-start:[dynamodb.java2.get_item_index.main]
public static void queryIndex(DynamoDbClient ddb, String tableName) {
try {
// Create a DynamoDbEnhancedClient and use the DynamoDbClient object.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(ddb)
.build();
//Create a DynamoDbTable object based on Movies.
DynamoDbTable<Movies> table = enhancedClient.table("Movies", TableSchema.fromBean(Movies.class));
String dateVal = "2013";
DynamoDbIndex<Movies> secIndex =
enhancedClient.table("Movies", TableSchema.fromBean(Movies.class))
.index("year-index");
AttributeValue attVal = AttributeValue.builder()
.n(dateVal)
.build();
// Create a QueryConditional object that's used in the query operation.
QueryConditional queryConditional = QueryConditional
.keyEqualTo(Key.builder().partitionValue(attVal)
.build());
// Get items in the table.
SdkIterable<Page<Movies>> results = secIndex.query(
QueryEnhancedRequest.builder()
.queryConditional(queryConditional)
.limit(300)
.build());
//Display the results.
results.forEach(page -> {
List<Movies> allMovies = page.items();
for (Movies myMovies: allMovies) {
System.out.println("The movie title is " + myMovies.getTitle() + ". The year is " + myMovies.getYear());
}
});
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
// snippet-end:[dynamodb.java2.get_item_index.main]
}
This now returns all Movies where the year is 2013.
Second way - Use the V2 Service Client
package com.example.dynamodb;
// snippet-start:[dynamodb.java2.query_items_sec_index.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
import java.util.HashMap;
import java.util.Map;
// snippet-end:[dynamodb.java2.query_items_sec_index.import]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
*
* For more information, see the following documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*
* Create the Movies table by running the Scenario example and loading the Movies data from the JSON file. Next create a secondary
* index for the Movies table that uses only the year column. Name the index **year-index**. For more information, see:
*
* https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html
*/
public class QueryItemsUsingIndex {
public static void main(String[] args) {
String tableName = "Movies" ; //args[0];
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create();
Region region = Region.US_EAST_1;
DynamoDbClient ddb = DynamoDbClient.builder()
.credentialsProvider(credentialsProvider)
.region(region)
.build();
queryIndex(ddb, tableName);
ddb.close();
}
// snippet-start:[dynamodb.java2.query_items_sec_index.main]
public static void queryIndex(DynamoDbClient ddb, String tableName) {
try {
Map<String,String> expressionAttributesNames = new HashMap<>();
expressionAttributesNames.put("#year","year");
Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
expressionAttributeValues.put(":yearValue", AttributeValue.builder().n("2013").build());
QueryRequest request = QueryRequest.builder()
.tableName(tableName)
.indexName("year-index")
.keyConditionExpression("#year = :yearValue")
.expressionAttributeNames(expressionAttributesNames)
.expressionAttributeValues(expressionAttributeValues)
.build();
System.out.println("=== Movie Titles ===");
QueryResponse response = ddb.query(request);
response.items()
.forEach(movie-> System.out.println(movie.get("title").s()));
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
// snippet-end:[dynamodb.java2.query_items_sec_index.main]
}
**Third way - Use PartQL
Of course, you can query the partition key using PartiQL. For example.
public static void queryTable(DynamoDbClient ddb) {
String sqlStatement = "SELECT * FROM MoviesPartiQ where year = ? ORDER BY info";
try {
List<AttributeValue> parameters = new ArrayList<>();
AttributeValue att1 = AttributeValue.builder()
.n(String.valueOf("2013"))
.build();
parameters.add(att1);
// Get items in the table and write out the ID value.
ExecuteStatementResponse response = executeStatementRequest(ddb, sqlStatement, parameters);
System.out.println("ExecuteStatement successful: "+ response.toString());
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}

How do we hash/encrypt multiple columns value into aws dynamodb?

I am currently working on a payment api that will stored the values into aws dynamodb.
Currently, i have managed to link it together with aws but now i'm just wondering if there is any method such that i am able to encrypt multiple columns value (like payeeName and payeeCardNo) before storing into the aws dynamodb??
Below is my code:
package com.example.payment.model;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#DynamoDBTable(tableName = "CitiBank")
public class CitiBank {
#DynamoDBHashKey
#DynamoDBAutoGeneratedKey
private String paymentID;
// payment Information
#DynamoDBAttribute
private String payeeName;
#DynamoDBAttribute
private String payeeCardNo;
#DynamoDBAttribute
private int price;
public String getPaymentID(){
return paymentID;
}
public void setPaymentID(String paymentID){
this.paymentID = paymentID;
}
public String getPayeeName(){
return payeeName;
}
public void setPayeeName(String payeeName){
this.payeeName = payeeName;
}
public String getPayeeCardNo(){
return payeeCardNo;
}
public void setPayeeCardNo(String payeeCardNo){
this.payeeCardNo = payeeCardNo;
}
public int getPrice(){
return price;
}
public void setPrice(int price){
this.price = price;
}
You can use the KMS service Java API to encrypt data and then store the encrypted data in DynamoDB. Here is a Java V2 example that shows you how to encrpt/decrypt data.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/javav2/example_code/kms/src/main/java/com/example/kms/EncryptDataKey.java
Also - not sure that you are aware that a newer API exists for Java and AWS. You are using an older version that the AWS SDK team recommends moving away from. They recommend using the DynamoDB V2 API.
The AWS SDK for Java 2.x is a major rewrite of the version 1.x code base. It’s built on top of Java 8+ and adds several frequently requested features. These include support for non-blocking I/O and the ability to plug in a different HTTP implementation at run time.
Just tested this use case and it worked. I encrypted the data using a KMS key prior to writing it to the table.
Java V2 code that encrypts the albumTitleValue data and then adds it to the DynamoDB table:
public static void putItemInTable(DynamoDbClient ddb,
KmsClient kmsClient,
String tableName,
String key,
String keyVal,
String albumTitle,
String albumTitleValue,
String awards,
String awardVal,
String songTitle,
String songTitleVal,
String keyId ){
HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();
//Encrypt the albumTitleValue before writing it to the table
SdkBytes myBytes = SdkBytes.fromUtf8String(albumTitleValue);
EncryptRequest encryptRequest = EncryptRequest.builder()
.keyId(keyId)
.plaintext(myBytes)
.build();
EncryptResponse response = kmsClient.encrypt(encryptRequest);
// Get the encrypted data
SdkBytes encryptedData = response.ciphertextBlob();
// Add content to the table
itemValues.put(key, AttributeValue.builder().s(keyVal).build());
itemValues.put(songTitle, AttributeValue.builder().s(songTitleVal).build());
itemValues.put(albumTitle, AttributeValue.builder().bs(encryptedData).build());
itemValues.put(awards, AttributeValue.builder().s(awardVal).build());
PutItemRequest request = PutItemRequest.builder()
.tableName(tableName)
.item(itemValues)
.build();
try {
ddb.putItem(request);
System.out.println(tableName +" was successfully updated");
} catch (ResourceNotFoundException e) {
System.err.format("Error: The Amazon DynamoDB table \"%s\" can't be found.\n", tableName);
System.err.println("Be sure that it exists and that you've typed its name correctly!");
System.exit(1);
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
You can find additional DynamoDB Java V2 examples here:
https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javav2/example_code/dynamodb

Get AWS Cognito user ID in Serverless Java function

I am following the https://serverless-stack.com/ tutorial which uses the Serverless framework to create an API that inserts objects into a DynamoDB table and associates them to the authenticated AWS Cognito user. I am attempting to convert the Node.js code to Java but I have hit a problem when getting the Cognito identity as shown on this page
userId: event.requestContext.identity.cognitoIdentityId,
I expected the following lines of Java code to be equivalent:
final CognitoIdentity identity = context.getIdentity();
final String userId = identity.getIdentityId();
but userId is empty.
I am using the aws-api-gateway-cli-test utility to call my API with credentials of a Cognito user as shown on this page. The authentication passes but the userId is empty in the handler.
This is my function:
package com.mealplanner.function;
import java.util.Map;
import com.amazonaws.services.lambda.runtime.CognitoIdentity;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mealplanner.dal.MealRepository;
import com.mealplanner.domain.Meal;
import com.serverless.ApiGatewayResponse;
public class CreateMealHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
#Override
public ApiGatewayResponse handleRequest(final Map<String, Object> request, final Context context) {
try {
final CognitoIdentity identity = context.getIdentity();
final String userId = identity.getIdentityId();
final JsonNode body = new ObjectMapper().readTree((String) request.get("body"));
final MealRepository repository = new MealRepository();
final Meal meal = new Meal();
meal.setUserId(userId);
meal.setDescription(body.get("description").asText());
repository.save(meal);
return ApiGatewayResponse.builder()
.setStatusCode(200)
.setObjectBody(meal)
.build();
} catch (final Exception e) {
final String errorText = String.format("Error saving meal with request [%s]", request);
LOGGER.error(errorText, e);
return ApiGatewayResponse.builder()
.setStatusCode(500)
.setObjectBody(errorText)
.build();
}
}
}
And this is the function definition in serverless.yml:
createMeal:
handler: com.mealplanner.function.CreateMealHandler
events:
- http:
path: /meals
method: post
cors: true
authorizer: aws_iam
Am I missing some configuration or have I not translated the Node.js code correctly?
In case I have missed any pertinent information the full code is available here: https://github.com/stuartleylandcole/meal-planner/tree/add-users. I will update this question with anything that is missing to ensure all relevant information is self-contained.
It turns out I hadn't translated the Node.js code correctly. To access the CognitoIdentityId I had to get the requestContext from the request object, then get the identity object, like so:
public ApiGatewayResponse handleRequest(final Map<String, Object> request, final Context context) {
final Map<String, Object> requestContext = (Map<String, Object>) request.get("requestContext");
final Map<String, Object> identity = (Map<String, Object>) requestContext.get("identity");
final String userId = (String) identity.get("cognitoIdentityId");
// etc
}

How to call an aws java lambda function from another AWS Java Lambda function when both are in same account, same region

I have a java aws lambda function or handler as AHandler that does some stuff e.g. It has been subscribed to SNS events, It parses that SNS event and log relevant data to the database.
I have another java aws lambda BHandler, Objective of this BHandler to receive a request from AHandler and provide a response back to AHandler. Because BHandler's objective is to provide a response with some json data. and that would be used by the AHandler.
May I see any clear example which tells how we can do such things ?
I saw this example call lambda function from a java class and Invoke lambda function from java
My question talks about that situation, when one aws java lambda function (or handler) calls to another aws java lambda function when both are in same region, same account,same vpc execution stuff, same rights. In that case aws java lambda function can directly call( or invoke) to another or still it has to provide aws key,region etc stuff (as in above links) ? A clear example/explanation would be very helpful.
EDIT
The AHandler who is calling another Lambda function (BHandler) , exist on same account have given complete AWSLambdaFullAccess with everything e.g.
“iam:PassRole",
"lambda:*",
Here is the code to call :
Note : Below code works when I call the same function with everything same from a normal java main function. But its not working like calling from on lambda function (like ALambdaHandler calling BLambdaHandler as a function call). Even its not returning any exception. Its just showing timeout, its got stuck at the code of: lambdaClient.invoke
String awsAccessKeyId = PropertyManager.getSetting("awsAccessKeyId");
String awsSecretAccessKey = PropertyManager.getSetting("awsSecretAccessKey");
String regionName = PropertyManager.getSetting("regionName");
String geoIPFunctionName = PropertyManager.getSetting("FunctionName");
Region region;
AWSCredentials credentials;
AWSLambdaClient lambdaClient;
credentials = new BasicAWSCredentials(awsAccessKeyId,
awsSecretAccessKey);
lambdaClient = (credentials == null) ? new AWSLambdaClient()
: new AWSLambdaClient(credentials);
region = Region.getRegion(Regions.fromName(regionName));
lambdaClient.setRegion(region);
String returnGeoIPDetails = null;
try {
InvokeRequest invokeRequest = new InvokeRequest();
invokeRequest.setFunctionName(FunctionName);
invokeRequest.setPayload(ipInput);
returnDetails = byteBufferToString(
lambdaClient.invoke(invokeRequest).getPayload(),
Charset.forName("UTF-8"),logger);
} catch (Exception e) {
logger.log(e.getMessage());
}
EDIT
I did everything as suggested by others and followed everything. At the end I reached to AWS support, and the problem was related to some VPC configurations stuff, and that got solved.If you have encountered similar stuff, then may be check security configs, VPC stuff.
We have achieved this by using com.amazonaws.services.lambda.model.InvokeRequest.
Here is code sample.
public class LambdaInvokerFromCode {
public void runWithoutPayload(String functionName) {
runWithPayload(functionName, null);
}
public void runWithPayload(String functionName, String payload) {
AWSLambdaAsyncClient client = new AWSLambdaAsyncClient();
client.withRegion(Regions.US_EAST_1);
InvokeRequest request = new InvokeRequest();
request.withFunctionName(functionName).withPayload(payload);
InvokeResult invoke = client.invoke(request);
System.out.println("Result invoking " + functionName + ": " + invoke);
}
public static void main(String[] args) {
String KeyName ="41159569322017486.json";
String status = "success";
String body = "{\"bucketName\":\""+DBUtils.S3BUCKET_BULKORDER+"\",\"keyName\":\""+KeyName+"\", \"status\":\""+status+"\"}";
System.out.println(body);
JSONObject inputjson = new JSONObject(body);
String bucketName = inputjson.getString("bucketName");
String keyName = inputjson.getString("keyName");
String Status = inputjson.getString("status");
String destinationKeyName = keyName+"_"+status;
LambdaInvokerFromCode obj = new LambdaInvokerFromCode();
obj.runWithPayload(DBUtils.FILE_RENAME_HANDLER_NAME,body);
}
}
Make sure the role which your Lambda function executes with has lambda:InvokeFunction permission.
Then use AWS SDK to invoke the 2rd function. (Doc: http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/lambda/AWSLambdaClient.html#invoke(com.amazonaws.services.lambda.model.InvokeRequest))
Edit: For such a scenario, consider using Step Functions.
We had similar problem and tried to gather various implementations to achieve this. Turns out it had nothing to do with the code.
Few basic rules:
Ensure proper policy and role for your lambda function, at minimum:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:::"
},
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
""
]
}
]
}
Have functions in same regions.
No VPC configurations needed. If your applications have VPC, make sure your lambda function has appropriate role policy (refer AWSLambdaVPCAccessExecutionRole)
Most important (primarily why it was failing for us), set right timeouts and heap sizes. Calling Lambda is going to wait until called one is finished. Simple math of 2x the called lambda values works. Also this was only with java lambda function calling another java lambda function. With node js lambda function calling another lambda function did not have this issue.
Following are some implementations that works for us:
Using service interface
import com.amazonaws.regions.Regions;
import com.amazonaws.services.lambda.AWSLambdaAsyncClientBuilder;
import com.amazonaws.services.lambda.invoke.LambdaInvokerFactory;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaFunctionHandler implements RequestHandler {
#Override
public String handleRequest(Object input, Context context) {
context.getLogger().log("Input: " + input);
FineGrainedService fg = LambdaInvokerFactory.builder()
.lambdaClient(
AWSLambdaAsyncClientBuilder.standard()
.withRegion(Regions.US_EAST_2)
.build()
)
.build(FineGrainedService.class);
context.getLogger().log("Response back from FG" + fg.getClass());
String fgRespone = fg.callFineGrained("Call from Gateway");
context.getLogger().log("fgRespone: " + fgRespone);
// TODO: implement your handler
return "Hello from Gateway Lambda!";
}
}
import com.amazonaws.services.lambda.invoke.LambdaFunction;
public interface FineGrainedService {
#LambdaFunction(functionName="SimpleFineGrained")
String callFineGrained(String input);
}
Using invoker
import java.nio.ByteBuffer;
import com.amazonaws.services.lambda.AWSLambdaClient;
import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaFunctionHandler implements RequestHandler {
#Override
public String handleRequest(Object input, Context context) {
context.getLogger().log("Input: " + input);
AWSLambdaClient lambdaClient = new AWSLambdaClient();
try {
InvokeRequest invokeRequest = new InvokeRequest();
invokeRequest.setFunctionName("SimpleFineGrained");
invokeRequest.setPayload("From gateway");
context.getLogger().log("Before Invoke");
ByteBuffer payload = lambdaClient.invoke(invokeRequest).getPayload();
context.getLogger().log("After Inoke");
context.getLogger().log(payload.toString());
context.getLogger().log("After Payload logger");
} catch (Exception e) {
// TODO: handle exception
}
// TODO: implement your handler
return "Hello from Lambda!";
}
}
AWSLambdaClient should be created from builder.
You can use LambdaClient to invoke Lambda asynchronously by passing InvocationType.EVENT parameter. Look at an example:
LambdaClient lambdaClient = LambdaClient.builder().build();
InvokeRequest invokeRequest = InvokeRequest.builder()
.functionName("functionName")
.invocationType(InvocationType.EVENT)
.payload(SdkBytes.fromUtf8String("payload"))
.build();
InvokeResponse response = lambdaClient.invoke(invokeRequest);

JWT (JSON Web Token) library for Java [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 3 years ago.
Improve this question
I am working on a web application developed using Java and AngularJS and chose to implement token authentication and authorization.
For the exercise purpose, I've come to the point where I send the credentials to the server, generate a random token store it and send it back to the client.
At every request to the server I'm attaching the token in the header and it works perfectly.
For the authentication point of view is perfect and wouldn't need more.
However, I now want to keep track of the user type (admin, regular user...), as well as it's id, or any other unique field; as I understood I have to encrypt that in the token that I'm sending back to the client during the log in action. Is that correct?
Is there any JWT library that you used and can generate, encrypt and decrypt such tokens?
A link to the library's API and Maven dependency would be much appreciated.
Thanks
JJWT aims to be the easiest to use and understand JWT library for the JVM and Android:
https://github.com/jwtk/jjwt
If anyone in the need for an answer,
I used this library: http://connect2id.com/products/nimbus-jose-jwt
Maven here: http://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt/2.10.1
By referring to https://jwt.io/ you can find jwt implementations in many languages including java. Also the site provide some comparison between these implementation (the algorithms they support and ....).
For java these are mentioned libraries:
https://github.com/jwtk/jjwt
https://github.com/auth0/java-jwt ( A tutorial at https://auth0.com/docs/server-apis/java)
https://bitbucket.org/b_c/jose4j
https://bitbucket.org/connect2id/nimbus-jose-jwt
This library seems to work well: https://code.google.com/p/jsontoken/ .
It depends on Google Guava. Here are the Maven artifacts:
<dependency>
<groupId>com.googlecode.jsontoken</groupId>
<artifactId>jsontoken</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
The library is in fact used by Google Wallet.
Here is how to create a jwt, and to verify it and deserialize it:
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.util.Calendar;
import java.util.List;
import net.oauth.jsontoken.JsonToken;
import net.oauth.jsontoken.JsonTokenParser;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;
import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
import net.oauth.jsontoken.crypto.SignatureAlgorithm;
import net.oauth.jsontoken.crypto.Verifier;
import net.oauth.jsontoken.discovery.VerifierProvider;
import net.oauth.jsontoken.discovery.VerifierProviders;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
/**
* Provides static methods for creating and verifying access tokens and such.
* #author davidm
*
*/
public class AuthHelper {
private static final String AUDIENCE = "NotReallyImportant";
private static final String ISSUER = "YourCompanyOrAppNameHere";
private static final String SIGNING_KEY = "LongAndHardToGuessValueWithSpecialCharacters#^($%*$%";
/**
* Creates a json web token which is a digitally signed token that contains a payload (e.g. userId to identify
* the user). The signing key is secret. That ensures that the token is authentic and has not been modified.
* Using a jwt eliminates the need to store authentication session information in a database.
* #param userId
* #param durationDays
* #return
*/
public static String createJsonWebToken(String userId, Long durationDays) {
//Current time and signing algorithm
Calendar cal = Calendar.getInstance();
HmacSHA256Signer signer;
try {
signer = new HmacSHA256Signer(ISSUER, null, SIGNING_KEY.getBytes());
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
//Configure JSON token
JsonToken token = new net.oauth.jsontoken.JsonToken(signer);
token.setAudience(AUDIENCE);
token.setIssuedAt(new org.joda.time.Instant(cal.getTimeInMillis()));
token.setExpiration(new org.joda.time.Instant(cal.getTimeInMillis() + 1000L * 60L * 60L * 24L * durationDays));
//Configure request object, which provides information of the item
JsonObject request = new JsonObject();
request.addProperty("userId", userId);
JsonObject payload = token.getPayloadAsJsonObject();
payload.add("info", request);
try {
return token.serializeAndSign();
} catch (SignatureException e) {
throw new RuntimeException(e);
}
}
/**
* Verifies a json web token's validity and extracts the user id and other information from it.
* #param token
* #return
* #throws SignatureException
* #throws InvalidKeyException
*/
public static TokenInfo verifyToken(String token)
{
try {
final Verifier hmacVerifier = new HmacSHA256Verifier(SIGNING_KEY.getBytes());
VerifierProvider hmacLocator = new VerifierProvider() {
#Override
public List<Verifier> findVerifier(String id, String key){
return Lists.newArrayList(hmacVerifier);
}
};
VerifierProviders locators = new VerifierProviders();
locators.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocator);
net.oauth.jsontoken.Checker checker = new net.oauth.jsontoken.Checker(){
#Override
public void check(JsonObject payload) throws SignatureException {
// don't throw - allow anything
}
};
//Ignore Audience does not mean that the Signature is ignored
JsonTokenParser parser = new JsonTokenParser(locators,
checker);
JsonToken jt;
try {
jt = parser.verifyAndDeserialize(token);
} catch (SignatureException e) {
throw new RuntimeException(e);
}
JsonObject payload = jt.getPayloadAsJsonObject();
TokenInfo t = new TokenInfo();
String issuer = payload.getAsJsonPrimitive("iss").getAsString();
String userIdString = payload.getAsJsonObject("info").getAsJsonPrimitive("userId").getAsString();
if (issuer.equals(ISSUER) && !StringUtils.isBlank(userIdString))
{
t.setUserId(new ObjectId(userIdString));
t.setIssued(new DateTime(payload.getAsJsonPrimitive("iat").getAsLong()));
t.setExpires(new DateTime(payload.getAsJsonPrimitive("exp").getAsLong()));
return t;
}
else
{
return null;
}
} catch (InvalidKeyException e1) {
throw new RuntimeException(e1);
}
}
}
public class TokenInfo {
private ObjectId userId;
private DateTime issued;
private DateTime expires;
public ObjectId getUserId() {
return userId;
}
public void setUserId(ObjectId userId) {
this.userId = userId;
}
public DateTime getIssued() {
return issued;
}
public void setIssued(DateTime issued) {
this.issued = issued;
}
public DateTime getExpires() {
return expires;
}
public void setExpires(DateTime expires) {
this.expires = expires;
}
}
This is based on code here: https://developers.google.com/wallet/instant-buy/about-jwts
And Here: https://code.google.com/p/wallet-online-sample-java/source/browse/src/com/google/wallet/online/jwt/util/WalletOnlineService.java?r=08b3333bd7260b20846d7d96d3cf15be8a128dfa
IETF has suggested jose libs on it's wiki:
http://trac.tools.ietf.org/wg/jose/trac/wiki
I would highly recommend using them for signing. I am not a Java guy, but seems like jose4j seems like a good option. Has nice examples as well: https://bitbucket.org/b_c/jose4j/wiki/JWS%20Examples
Update: jwt.io provides a neat comparison of several jwt related
libraries, and their features. A must check!
I would love to hear about what other java devs prefer.
I found this to be small and complete https://github.com/auth0/java-jwt
This page keeps references to implementations in various languages, including Java, and compares features: http://kjur.github.io/jsjws/index_mat.html
If you only need to parse unsigned unencrypted tokens you could use this code:
boolean parseJWT_2() {
String authToken = getToken();
String[] segments = authToken.split("\\.");
String base64String = segments[1];
int requiredLength = (int)(4 * Math.ceil(base64String.length() / 4.0));
int nbrPaddings = requiredLength - base64String.length();
if (nbrPaddings > 0) {
base64String = base64String + "====".substring(0, nbrPaddings);
}
base64String = base64String.replace("-", "+");
base64String = base64String.replace("_", "/");
try {
byte[] data = Base64.decode(base64String, Base64.DEFAULT);
String text;
text = new String(data, "UTF-8");
tokenInfo = new Gson().fromJson(text, TokenInfo.class);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
https://github.com/networknt/jsontoken
This is a fork of original google jsontoken
It has not been updated since Sep 11, 2012 and depends on some old packages.
What I have done:
Convert from Joda time to Java 8 time. So it requires Java 8.
Covert Json parser from Gson to Jackson as I don't want to include two Json parsers to my projects.
Remove google collections from dependency list as it is stopped long time ago.
Fix thread safe issue with Java Mac.doFinal call.
All existing unit tests passed along with some newly added test cases.
Here is a sample to generate token and verify the token. For more information, please check https://github.com/networknt/light source code for usage.
I am the author of both jsontoken and Omni-Channel Application Framework.

Categories