Syntax highlighter header

Thursday, 14 August 2025

Java 21 with Spring Security 6.5.2, Oauth2 and AWS Cognito Pool

Recently we were trying to migrate our spring boot application to Java 21 and latest spring boot version to for enhancing security of our application. We hit major road blocks related to working of JWT tokens. Here I am documenting the solution which worked for us. Major idea is similar to my previous post https://blog.bigdatawithjasvant.com/2023/08/spring-security-60-with-oauth2-and-aws.html

But there were some new challenges which we faced. Let us get started.

We were having some public apis which were open to all without any authentication and some apis were secured with Cognito pool JWT token. This application was different from the one described in my previous post. So we were not building on top of application described in my previous post. For configuring access to apis you need to create a configuration class called SecurityConfiguration like below:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Autowired
    private JwtDecoder jwtDecoder;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http.cors(c-> {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            config.addAllowedOriginPattern("*");
            config.addAllowedHeader("*");
            config.setAllowedMethods(Arrays.asList("OPTIONS", "GET", "POST", "PUT", "DELETE", "PATCH"));
            source.registerCorsConfiguration("/**", config);

            c.configurationSource(source);
        });
        http.csrf(AbstractHttpConfigurer::disable);

        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/","/api/public/**","/actuator/**").permitAll()
                        .anyRequest().authenticated()
                ).sessionManagement(sm-> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(rs->  rs.jwt(j-> j.decoder(jwtDecoder)));
        // @formatter:on
        return http.build();
    }

}

For decoding JWT tokens we need to use JwtDecoder. The JWT decoder is defined in another config class like this:

import com.nimbusds.jose.*;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.security.Key;
import java.util.*;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.text.ParseException;

@Configuration
public class JwtConfiguration {

    public static final String COGNITO_GROUPS = "cognito:groups";
    private static final String SPRING_AUTHORITIES = "scope";
    public static final String COGNITO_USERNAME = "username";
    private static final String SPRING_USER_NAME = "sub";

    @Value("${security.oauth2.resource.jwk.key-set-uri}")
    private String keySetUri;

    @Bean
    JwtDecoder jwtDecoder() throws ParseException {
        // Obtain the JWKS from the endpoint
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> jwksResponse = restTemplate.getForEntity(keySetUri, String.class);
        String jwksJson = jwksResponse.getBody();

        JWKSet jwkSet = JWKSet.parse(jwksJson);

        DefaultJWTProcessor<SecurityContext> defaultJWTProcessor =  new DefaultJWTProcessor<>();

        defaultJWTProcessor.setJWSKeySelector(new JWSKeySelector<SecurityContext>() {
            @Override
            public List<? extends Key> selectJWSKeys(JWSHeader header, SecurityContext context) throws KeySourceException {
                RSAKey rsaKey = (RSAKey) jwkSet.getKeyByKeyId(header.getKeyID());
                try {
                    return new ArrayList<Key>(Arrays.asList(rsaKey.toPublicKey()));
                } catch (JOSEException e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
        NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(defaultJWTProcessor);

        Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = MappedJwtClaimSetConverter
                .withDefaults(Collections.emptyMap());

        jwtDecoder.setClaimSetConverter( claims -> {
            claims = claimSetConverter.convert(claims);

            HashMap<String, Object> hashMap = new HashMap<>(claims);
            if (claims.containsKey(COGNITO_GROUPS))
                ((Map<String, Object>) hashMap).put(SPRING_AUTHORITIES, claims.get(COGNITO_GROUPS));
            if (claims.containsKey(COGNITO_USERNAME))
                ((Map<String, Object>) hashMap).put(SPRING_USER_NAME, claims.get(COGNITO_USERNAME));

            return hashMap;
        });
        return jwtDecoder;
    }
}

When I tried running this code I faced a class not found exception which took a lot of time to resolve. The Exception was:

[ main] o.s.boot.SpringApplication , 857 : Application run failed java.lang.ClassNotFoundException: org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationProvider 
  at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) 
  at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) 
  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) 
  ... 57 common frames omitted 
Wrapped by: java.lang.NoClassDefFoundError: org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider

The reason of the problem was mismatch between different versions of spring security libraries. The problem was solved when I used following versions of the dependencies:

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.5.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
        <dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.15.2</version> <!-- match AWS SDK requirements -->
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
			<version>1.1.1.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-oauth2-client -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-oauth2-client</artifactId>
			<version>6.5.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>6.5.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-oauth2-resource-server</artifactId>
			<version>6.5.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-oauth2-jose</artifactId>
			<version>6.5.2</version>
		</dependency>
		<dependency>
			<groupId>com.nimbusds</groupId>
			<artifactId>nimbus-jose-jwt</artifactId>
			<version>10.0.2</version>
		</dependency>
      </dependencies>

Please comment if you find it useful or need some more info.

Friday, 8 November 2024

NewRelic error with spring boot

 Recently we added two datasources to our application and our application worked fine without newRelic. But as soon as we added newRelic to our application it failed with a strange error:

2024-11-08 20:22:37 java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
2024-11-08 20:22:37     at org.jboss.jandex.Indexer.updateTypeTarget(Indexer.java:903)
2024-11-08 20:22:37     at org.jboss.jandex.Indexer.updateTypeTargets(Indexer.java:630)
2024-11-08 20:22:37     at org.jboss.jandex.Indexer.index(Indexer.java:1698)
2024-11-08 20:22:37     at org.hibernate.boot.archive.scan.spi.ClassFileArchiveEntryHandler.toClassDescriptor(ClassFileArchiveEntryHandler.java:64)
2024-11-08 20:22:37     at org.hibernate.boot.archive.scan.spi.ClassFileArchiveEntryHandler.handleEntry(ClassFileArchiveEntryHandler.java:52)
2024-11-08 20:22:37     at org.hibernate.boot.archive.internal.JarFileBasedArchiveDescriptor.visitArchive(JarFileBasedArchiveDescriptor.java:147)
2024-11-08 20:22:37     at org.hibernate.boot.archive.scan.spi.AbstractScannerImpl.scan(AbstractScannerImpl.java:48)
2024-11-08 20:22:37     at org.hibernate.boot.model.process.internal.ScanningCoordinator.coordinateScan(ScanningCoordinator.java:76)
2024-11-08 20:22:37     at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.prepare(MetadataBuildingProcess.java:107)
2024-11-08 20:22:37     at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:269)
2024-11-08 20:22:37     at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:182)
2024-11-08 20:22:37     at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:52)
2024-11-08 20:22:37     at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
2024-11-08 20:22:37     at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
2024-11-08 20:22:37     at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
2024-11-08 20:22:37     at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
2024-11-08 20:22:37     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
2024-11-08 20:22:37     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)

After struggling for a lot of time we figured out that one of the datasource was not having any entity defined. After defining a dummy entity for the datasource the error was gone away and the application started working with newRelic also.

Please do comment if you find it useful.

Thursday, 24 October 2024

Adding CORS headers to python flask server

 Recently we were trying to add CORS headers to a python flask web server to allow our react website to be allowed to make call to APIs hosted in python flask server. Our aim was to make least amount of code change to server and incorporate CORS headers in all the APIs hosted on this server. We tried for 2-3 days and found a really simple ways of doing it.

Here I am sharing the solution. We added a after request handler in flask to add required headers to all the responses after the response is generated.


from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

@app.after_request
def add_cors_header(response):
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Headers"] = "*"
    return response

if __name__ == '__main__':
    app.run(debug=False)

Monday, 15 April 2024

Allowing Client to Client connect on VPN

Recently we were trying to debug an application with two developers one backend developer and one frontend developer. We were working in work from home setup so both were working from home.

We were wasting a lots of effort in pushing backed changes again and again to our staging server for testing. We were thinking that it would be great if frontend developer could connect to service running on backend developer's machine directly and debug the problem. We tried connecting using private IP address of backend developer's machine but connection timed out.

Later on we discovered that there was a configuration needed on OpenVpn server. You need to enable following configuration on OpenVpn server to enable client to client connection. We found this functionality to be of a great help in debugging.

client-to-client

Following post was very useful https://serverfault.com/questions/570316/how-can-multiple-clients-of-an-openvpn-server-find-each-other

Same can be achieved in AWS client VPN also https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/scenario-client-to-client.html

Tuesday, 12 March 2024

java.time.ZonedDateTime not supported in ObjectMapper

Recently I was trying to serialize a Java object into json using ObjectMapper but encountered the following exception

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.ZonedDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

The reason was that Java time module is not supported by default and it does not work just by adding maven dependency. The following code worked for me.

objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);