DEV Community

Ed Legaspi
Ed Legaspi

Posted on • Edited on • Originally published at czetsuyatech.com

1

Spring Method Level Security with Amazon Cognito and JWT Token

Learn how to authenticate your user with AWS Cognito and secure your Spring REST endpoints with JWT token at the method level using Spring Security.

1. Introduction

The goal of this tutorial is to authenticate and authorize a user in a Spring REST service using the JWT token.

1.1 Prerequisite

  • Create a user pool in Amazon Cognito.
  • Create an application in Google Console.
    • Add a user/email that we can authenticate later for testing.

Follow this guide Next with AWS Cognito in case you are not familiar with them.

1.2 Goal - Create a Java Library with the following Features

  • Decoding an AWS Cognito JWT token.
  • Verifying the JWT token issuer.
  • Creating a custom SimpleCtAccount using the information contained in the JWT token.
  • Convert the associated Cognito groups into a custom CtRole.
  • A single interface to enable security.

2. The Spring Security Module

Spring has provided a lot of utility classes that we can use to secure our web application. One such class that we will use in our library is the WebSecurityConfigurerAdapter class to customize our HTTP and Web Security.

3. Class Diagram

3.1 Method Security

What if we wanted to do a finely detailed check on a particular method? For example, is this user living in the Philippines? Or is this user a member of a particular group?

This is where the Spring class GlobalMethodSecurityConfiguration comes in. First, let's take a look at the class diagram.

Image description

In this diagram, most of the common authorization checks are defined in CtMethodSecurityExpressionRoot. This class is extended by DefaultMethodSecurityExpressionRoot which we can extend on our microservice to provide additional authorization checks (This will be covered later).

To inform Spring and pickup this configuration, we need to annotate the class CtMethodSecurityConfiguration with: @Configuration and @EnableGlobalMethodSecurity(prePostEnabled = true).

4. How to use our Library?

4.1 Service Integration

I have uploaded this library on maven central for convenience. Also, the source code is 100% open-source on GitHub.

To use on a project:

  1. Add a maven dependency:
<dependency>
    <groupId>com.czetsuyatech</groupId>
    <artifactId>ct-iam-spring-security</artifactId>
    <version>LATEST-SNAPSHOT</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode
  1. Extend the class DefaultMethodSecurityExpressionRoot, and add more authorization checks.
public class CtAppMethodSecurityExpressionExtension extends DefaultMethodSecurityExpressionRoot {

  public CtAppMethodSecurityExpressionExtension(Authentication authentication) {
    super(authentication);
  }

  public boolean isAuthorized() {
    return true;
  }

  public boolean isUnAuthorized() {
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Create a Configuration class CtAppSecurityConfiguration where we will initialize the HTTP security and produce the CtMethodSecurityExpressionHandler bean.
@Configuration
@RequiredArgsConstructor
@EnableCtSecurity
public class CtAppSecurityConfiguration {

  @Bean
  public CtHttpSecurityConfigurer httpSecurityConfig() {

    return http ->
        http
            .authorizeHttpRequests(authz -> authz
                .antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
                .antMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            );
  }

  @Bean
  public CtMethodSecurityExpressionHandlerFactory methodSecurityFactory() {
    return CtAppMethodSecurityExpressionRoot::new;
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to annotate this class with @EnableCtSecurity.

4.2 Secured Endpoints

Create a new controller with the following endpoints and use the @PreAuthorize annotation.

@RestController
@Validated
@Slf4j
@RequiredArgsConstructor
public class ApiTestController {

  @GetMapping("/hello")
  @ResponseStatus(HttpStatus.OK)
  public String hello(@CurrentSecurityContext(expression = "authentication") Authentication auth) {

    log.debug("" + auth.getPrincipal());
    log.debug("" + auth.getCredentials());
    log.debug("" + auth.getDetails());

    return "Hello " + auth.getPrincipal();
  }

  @GetMapping("/api/testing/authenticated")
  @PreAuthorize("isAuthenticated()")
  @ResponseStatus(HttpStatus.OK)
  public String authenticated(@CurrentSecurityContext(expression = "authentication") Authentication auth) {

    log.debug("" + auth.getPrincipal());
    log.debug("" + auth.getCredentials());
    log.debug("" + auth.getDetails());

    return "Hello " + auth.getPrincipal();
  }

  @GetMapping("/api/testing/authorized")
  @PreAuthorize("isAuthorized()")
  @ResponseStatus(HttpStatus.OK)
  public String authorized() {
    return "authorized";
  }

  @GetMapping("/api/testing/unauthorized")
  @PreAuthorize("isUnAuthorized()")
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public String unAuthorized() {
    return "unauthorized";
  }
}
Enter fullscreen mode Exit fullscreen mode

5. References

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay