Search code examples
amazon-web-servicesaws-api-gatewayaws-java-sdk-2.x

Why do I get a 403 error when connecting to an AWS API Gateway websocket API using the AWS SDK?


I’m currently developing a Spring application and have encountered an issue while trying to post a message to a Websocket connection.

I’ve successfully set up a Websocket API in AWS API Gateway and can post to a specific connection using my IAM credentials via Postman.

However, when I attempt to do the same using the Java AWS SDK within my Spring application, I receive a 403 error.

Here is the snippet of code in Spring:

AwsSessionCredentials awsCreds = AwsSessionCredentials.create(
        "***********",
        "*********",
        "**********"
);

AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(awsCreds);

ApiGatewayManagementApiClient testApiClient = ApiGatewayManagementApiClient.builder()
        .region(Region.US_WEST_2)
        .credentialsProvider(credentialsProvider)
        .endpointOverride(new URI("https://api-id.execute-api.us-west-2.amazonaws.com/@connections/stage"))
        .build();

String connectionId = "********";

GetConnectionResponse getResponse = testApiClient.getConnection(GetConnectionRequest.builder()
        .connectionId(connectionId)
        .build());

PostToConnectionResponse postResponse = testApiClient.postToConnection(PostToConnectionRequest.builder()
        .connectionId(connectionId)
        .data(SdkBytes.fromUtf8String("test message from Spring"))
        .build());

The 1st request is attempting to get information about the connection.

The 2nd request is attempting to send a message to the connection. Both return a 403 error.

I know that my credentials have the correct permissions as I am able to directly send the same request via Postman with the same credentials and it works.

What is the issue?


Solution

  • TLDR: Use endpointOverride value of https://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}


    You don't need to specify @connections within the endpointOverride.

    Per the Javadoc, the SDK endpoint override follows the format of

    https://{api-id}.execute-api.{region}.amazonaws.com/{stage}

    not:

    https://{api-id}.execute-api.{region}.amazonaws.com/@connections/{stage}

    When using the SDK, the getConnection & postToConnection methods (as well as the deleteConnection method) will automatically append @connections to the endpoint of your deployed API.

    GetConnectionRequestMarshaller.class:

    @SdkInternalApi
    public class GetConnectionRequestMarshaller implements Marshaller<GetConnectionRequest> {
        // ...
    
        static {
            SDK_OPERATION_BINDING = OperationInfo
                .builder()
                .requestUri("/@connections/{connectionId}")
                .httpMethod(SdkHttpMethod.GET)
                .hasExplicitPayloadMember(false)
                .hasImplicitPayloadMembers(false)
                .hasPayloadMembers(false)
                .build();
        }
    }
    

    PostToConnectionRequestMarshaller.class:

    @SdkInternalApi
    public class PostToConnectionRequestMarshaller implements Marshaller<PostToConnectionRequest> {
        // ...
    
        static {
            SDK_OPERATION_BINDING = OperationInfo
                .builder()
                .requestUri("/@connections/{connectionId}")
                .httpMethod(SdkHttpMethod.POST)
                .hasExplicitPayloadMember(true)
                .hasImplicitPayloadMembers(false)
                .hasPayloadMembers(true)
                .build();
        }
    }
    

    This should work instead:

    URI endpointOverride = new URI("https://api-id.execute-api.us-west-2.amazonaws.com/stage");
    
    ApiGatewayManagementApiClient testApiClient =
        ApiGatewayManagementApiClient
            .builder()
            .region(Region.US_WEST_2)
            .credentialsProvider(credentialsProvider)
            .endpointOverride()
            .build();
    

    Even if you were to manually call the API without the SDK, the documented format is:

    https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/@connections/{connection_id}

    /@connections/{stage} won't work at all.