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
Follow this guide??https://www.czetsuyatech.com/2021/06/nextjs-security-with-aws-cognito.html ?in case you are not familiar with them.
1.2 Goal - Create a Java Library with the following Features
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.
n 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>>
2. 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;
??}
}{
3. Create a?Configuration?class??CtAppSecurityConfiguration?where we will initialize the?HTTP?security and produce the??CtMethodSecurityExpressionHandler?bean.
@Configuratio
@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;
??}
}n
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.
@RestControlle
@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";
??}
}
5. References
This article is originally published at https://www.czetsuyatech.com/2022/06/spring-method-level-security-with-amazon-cognito-and-jwt-token.html .