Decrypt & Validate JWE Tokens with Spring Security
Tags: jwe, java, springsecurity, security
Published: 2023-04-04 11:21:33

All systems that are exposed to a flood of requests must respond in a short time due to today's requirements. It is therefore important that the individual steps for processing a request are as fast and secure as possible. Therefore, stateless protocols such as OAuth 2.0 (or OIDC) are used for authentication purposes, for example. This protocol usually uses so-called [JWTs (JSON Web Token)](https://datatracker.ietf.org/doc/html/rfc7519). These tokens contain data about the user and his permissions in the system. The information is usually readable by anyone who has access to the communication and also by anyone involved in the connection between the user and the system (e.g. browser, proxy server or reverse proxy).

Usually, this is referred to as [JWS (JSON Web Signature)](https://datatracker.ietf.org/doc/html/rfc7515) tokens. To ensure that a potential change can be detected during transmission, these tokens are normally secured against this by signatures (immutability). In addition, there is another form of JWT, the [JWE (JSON Web Encryption)](https://datatracker.ietf.org/doc/html/rfc7516) tokens, which transmit the content in encrypted form (confidentiality). The encryption does not ensure who created the data. Therefore, when using JWE tokens, a JWS token is normally used as content to ensure the necessary security.

## What are JWE Tokens in a nutshell?

JWE tokens are basically a standardized way of exchanging structured data between systems in encrypted form. The RFC standard offers a wide variety of symmetrical and asymmetrical methods for encryption. The list of supported encryption algorithms can be found in the [RFC-7518](https://datatracker.ietf.org/doc/html/rfc7518#section-4.1). The payload to be transmitted contains a JWS token for stronger security, which ensures the immutability and correctness of the data.

### What is the structure of JWS and JWE tokens?

A JWS token consists of three parts separated by a dot (`.`) - `{header}.{payload}.{signature}`. The `header` contains, among other things, information about the algorithm used to create the signature and how it can be verified. The `payload` contains the data to be transmitted, and the last part contains the digital `signature`.

A JWE token consists of five parts separated by a dot (`.`) - `{Header}.{EncryptionKey}.{InitializationVector}.{Ciphertext}.{AuthenticationTag}`. The `header` contains, among other things, information about which algorithm was used to encrypt the data and whether there is a JWS in the data. The two parts `EncryptionKey` and `InitializationVector` contain technical data relevant to decrypting the data. The `Ciphertext` contains the encrypted data. The `AuthenticationTag` is used to ensure that the token has not been altered.

If you would like to know more about JWS and JWE tokens, you can read [this interesting article](https://medium.facilelogin.com/jwt-jws-and-jwe-for-not-so-dummies-b63310d201a3) about it or look at the respective RFC specifications.

## How can JWE tokens be decrypted and verified with Spring Boot or Spring Security?

Spring Boot in combination with Spring Security includes the functionality to include the verification of JWS and JWE tokens easily in the authentication process of the system.

Including the following dependency (e.g. via Maven) adds the functionality to the system.

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
```

Who is interested: The configuration in the Spring Boot Framework is done in the following classes.

* `org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration:jwtDecoderByJwkKeySetUri`
* `org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration`

### Including the JWS Token Check in Spring Boot

The following Spring Boot configuration shows how to verify the tokens:

```java
JwsSecurityConfiguration.java
@Configuration
@EnableWebSecurity
public class JwsSecurityConfiguration {}
```

```yaml
application.yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${url}/.well-known/jwks.json
          issuer-uri: ${url}
          audiences: resource-server-app
```

### Including the JWE Token Check in Spring Boot

To include the verification of JWE tokens, the following configuration can be used. Among other things, this requires a private key for decryption. In this example it is a RSA private key. Because of that, it must be defined for which algorithms the provided private key can be used for decryption. This configuration is largely based on an example project of the Spring Security Framework, which can be found [here](https://github.com/spring-projects/spring-security-samples/blob/main/servlet/spring-boot/java/oauth2/resource-server/jwe).

```java
JwtDecoderConfiguration.java
@Configuration
@EnableWebSecurity
public class JwtDecoderConfiguration {
    private final JWEAlgorithm jweAlgorithm = JWEAlgorithm.RSA_OAEP_256;
    private final EncryptionMethod encryptionMethod = EncryptionMethod.A256GCM;
    private final OAuth2ResourceServerProperties.Jwt properties;
    private final RSAPrivateKey key;

    JwtDecoderConfiguration(OAuth2ResourceServerProperties properties,
                            @Value("${sample.jwe-key-value}") RSAPrivateKey key) {
        this.properties = properties.getJwt();
        this.key = key;
    }

    @Bean
    JwtDecoder jwtDecoderByJwkKeySetUri() {
        NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
                .jwsAlgorithms(this::jwsAlgorithms)
                .jwtProcessorCustomizer(this::jwtProcessorCustomizer)
                .build();
        String issuerUri = this.properties.getIssuerUri();
        OAuth2TokenValidator<Jwt> defaultValidator = JwtValidators.createDefaultWithIssuer(issuerUri);
        nimbusJwtDecoder.setJwtValidator(getValidators(defaultValidator));
        return nimbusJwtDecoder;
    }

    private void jwsAlgorithms(Set<SignatureAlgorithm> signatureAlgorithms) {
        for (String algorithm : this.properties.getJwsAlgorithms()) {
            signatureAlgorithms.add(SignatureAlgorithm.from(algorithm));
        }
    }

    private OAuth2TokenValidator<Jwt> getValidators(OAuth2TokenValidator<Jwt> defaultValidator) {
        List<String> audiences = this.properties.getAudiences();
        List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
        validators.add(defaultValidator);
        validators.add(new JwtClaimValidator<List<String>>(JwtClaimNames.AUD,
                (aud) -> aud != null && !Collections.disjoint(aud, audiences)));
        return new DelegatingOAuth2TokenValidator<>(validators);
    }

    private void jwtProcessorCustomizer(ConfigurableJWTProcessor<SecurityContext> jwtProcessor) {
        JWKSource<SecurityContext> jweJwkSource = new ImmutableJWKSet<>(new JWKSet(rsaKey()));
        JWEKeySelector<SecurityContext> jweKeySelector = new JWEDecryptionKeySelector<>(this.jweAlgorithm,
                this.encryptionMethod, jweJwkSource);

        jwtProcessor.setJWEKeySelector(jweKeySelector);
    }

    private RSAKey rsaKey() {
        RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) this.key;
        Base64URL n = Base64URL.encode(crtKey.getModulus());
        Base64URL e = Base64URL.encode(crtKey.getPublicExponent());
        return new RSAKey.Builder(n, e)
                .privateKey(this.key)
                .keyUse(KeyUse.ENCRYPTION)
                .build();
    }
}
```

```yaml
application.yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json
          issuer-uri: ${mockwebserver.url}
          audiences: resource-server-app
sample:
  jwe-key-value: classpath:private.key
```

```bash
private.key
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
```

### Testing the authentication with JWE Tokens

To test the configuration for authentication, valid tokens must be created in the tests. These JWE tokens can be created with the following class. The contents etc. must be adapted to the respective requirements.

```java
@Component
public class TokenService {
    private final JWEAlgorithm jweAlgorithm = RSA_OAEP_256;
    private final EncryptionMethod encryptionMethod = A256GCM;
    private final JWSAlgorithm jwsAlgorithm = RS256;
    private final OAuth2ResourceServerProperties.Jwt properties;
    private final RSAPrivateKey key;

    TokenService(OAuth2ResourceServerProperties properties,
                            @Value("${sample.jwe-key-value}") RSAPrivateKey key) {
        this.properties = properties.getJwt();
        this.key = key;
    }

    public String buildToken() throws Exception {
        SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(jwsAlgorithm).build(), new JWTClaimsSet.Builder()
                .issuer(properties.getIssuerUri())
                .audience(properties.getAudiences())
                .subject("subject")
                .issueTime(new Date())
                .expirationTime(new Date(new Date().getTime() + 5000))
                .build());
        signedJWT.sign(new RSASSASigner(rsaSigningKey()));

        JWEObject jweObject = new JWEObject(
                new JWEHeader.Builder(jweAlgorithm, encryptionMethod)
                        .contentType("JWT")
                        .build(),
                new Payload(signedJWT));

        RSAEncrypter encrypter = new RSAEncrypter(rsaEncryptionKey());
        jweObject.encrypt(encrypter);

        return jweObject.serialize();
    }

    private RSAKey rsaEncryptionKey() {
        RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) this.key;
        Base64URL n = Base64URL.encode(crtKey.getModulus());
        Base64URL e = Base64URL.encode(crtKey.getPublicExponent());
        return new RSAKey.Builder(n, e)
                .keyUse(KeyUse.ENCRYPTION)
                .build();
    }

    private RSAKey rsaSigningKey() {
        RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) this.key;
        Base64URL n = Base64URL.encode(crtKey.getModulus());
        Base64URL e = Base64URL.encode(crtKey.getPublicExponent());
        return new RSAKey.Builder(n, e)
                .privateKey(crtKey)
                .keyUse(KeyUse.SIGNATURE)
                .build();
    }
}
```

## Summary

Based on our experience JWE tokens are not often used. Therefore, information regarding the usage in frameworks like Spring Boot and Spring Security is very rare. With this blog post we would like to make things easier for other developers who have to work with JWE Tokens and Spring Boot. Have fun :smiley:

---

Florian Storz is writing for the devlix Blog at https://www.devlix.de/blog
This article was published first here (german): https://www.devlix.de/jwe-tokens-mit-spring-security-entschluesseln-und-verifizieren/

![devlix logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmqebdbqusql1ibky1lr.png)
<figcaption>devlix GmbH: quality, consulting, development</figcaption>