If you're building an API that is intended for external clients, it's essential to have good documentation and authentication mechanisms in place. In this blog, we'll take a closer look at how we built a Spring Boot API and integrated it with AWS Cognito for authentication.
What is AWS Cognito?
AWS Cognito is a managed service from Amazon that provides authentication, authorization, and user management for web and mobile applications. It allows you to create and maintain a user directory and integrate it with your applications seamlessly. With AWS Cognito, you can handle user sign up, sign-in, and access control in a secure and scalable way.
What is AWS CDK?
AWS Cloud Development Kit (CDK) is an open-source software development framework that allows you to define your cloud infrastructure in code. With AWS CDK, you can define your cloud resources using familiar programming languages like TypeScript, Java, and Python, and then deploy them with ease. AWS CDK makes it easy to automate your infrastructure deployment and ensures that your resources are consistent and repeatable.
We deployed the Cognito stack via CDK. The following infrastructure was written in TypeScript and deployed via CDK: typescript
export class MyStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);
const userPool = new UserPool(this, 'UserPool', {
userPoolName: 'user-pool',
});
userPool.addDomain('user-pool-domain', {
cognitoDomain: {
domainPrefix: 'user-pool-domain
},
});
userPool.addResourceServer('resource-server', {
identifier: 'resource-server',
scopes: [
{
scopeName: 'resource-server/api',
scopeDescription: 'api access',
},
],
});
userPool.addClient('api-client', {
userPoolClientName: 'api-client',
generateSecret: true,
oAuth: {
flows: {
clientCredentials: true,
},
scopes: [
OAuthScope.custom('resource-server/api'),
],
},
});
}
}
After deploying the Cognito stack, we started a new Spring Boot service with the following dependencies:
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
Next, we created a controller to have an endpoint to test out the setup:
@RestController
@RequestMapping("/order")
class ShippingOrderController {
@PostMapping
fun createOrder(@RequestBody command: CreateOrderCommand) {
println("Creating order for $command")
}
}
Then we started configuring Spring Boot security. We used the @EnableWebSecurity
and @EnableMethodSecurity(prePostEnabled = true)
annotations to enable Spring Security and method-level security, respectively.
In the SecurityConfiguration
class, we defined an OAuth 2.0 security scheme and set up the security filter chain for our API. We also added the necessary configurations for the OpenAPI documentation.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@SecurityScheme(
name = "Authorization",
type = SecuritySchemeType.OAUTH2,
`in` = SecuritySchemeIn.HEADER,
flows = OAuthFlows(
clientCredentials = OAuthFlow(
authorizationUrl = "https://some.auth.eu-west-1.amazoncognito.com/oauth2/authorize",
tokenUrl = "https://some.auth.eu-west-1.amazoncognito.com/oauth2/token",
scopes = []
)
)
)
class SecurityConfiguration {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
return http
.csrf().disable() // we are not using any form based submits.
.authorizeHttpRequests()
.requestMatchers("/actuator/**").permitAll() // we want to access the actuator endpoints
.requestMatchers("/v3/api-docs/**").permitAll() // we want to access the swagger docs
.requestMatchers("/swagger-ui/**").permitAll() // we want to access the swagger docs
.anyRequest().authenticated() // any other request should be authenticated
.and()
.oauth2ResourceServer().jwt() // we are using jwt tokens
.and().and()
.build()
}
@Bean
fun nexusOpenApi(): OpenAPI? {
return OpenAPI()
.info(
Info()
.title("ATMS API")
.description("ATMS API")
.version("1.0.0")
)
.security(listOf(SecurityRequirement().addList("Authorization"))) // we want to use the Authorization security scheme
}
}
Finally, we set the issuer URL in the application.properties
file:
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_ourId
Now we can access our endpoint and do a login with the clientId
and clientSecret
, and it will automatically generate a token for us and set the correct headers.
In summary, integrating AWS Cognito with your Spring Boot API provides a secure and scalable way to handle authentication and authorization for your external clients. With the help of the springdoc module and AWS CDK, you can easily create good documentation and automate your infrastructure deployment.
Discussion (0)