解密并验证JWE令牌具有弹簧安全性
#安全 #java #springsecurity #jwe

由于当今的要求,所有暴露于大量请求的系统都必须在短时间内做出响应。因此,重要的是,要处理请求的单个步骤尽可能快,安全。因此,例如,将无状态协议(例如OAuth 2.0(或OIDC))用于身份验证目的。该协议通常使用所谓的JWTs (JSON Web Token)。这些令牌包含有关用户及其在系统中的权限的数据。该信息通常是可以访问通信以及用户与系统之间连接的任何人(例如浏览器,代理服务器或反向代理)的任何人。

通常,这被称为JWS (JSON Web Signature)令牌。为了确保可以在传输过程中检测到潜在的变化,通常通过签名(不可行性)对此确保这些令牌。此外,还有另一种形式的JWT,即JWE (JSON Web Encryption)令牌,它以加密形式(机密性)传输内容。加密不能确保谁创建数据。因此,使用JWE令牌时,JWS令牌通常被用作内容来确保必要的安全性。

简而言之,JWE令牌是什么?

jwe令牌基本上是一种标准化的方式,是在系统以加密形式之间交换结构化数据。 RFC标准提供了多种对称和不对称的加密方法。可以在RFC-7518中找到支持的加密算法列表。要发送的有效载荷包含一个JWS令牌,以确保数据的不可分割性和正确性。

JWS和JWE令牌的结构是什么?

JWs代币由三个部分组成,该部分由点(.) - {header}.{payload}.{signature}组成。 header包含有关用于创建签名及其如何验证的算法的信息。 payload包含要传输的数据,最后一部分包含数字signature

JWE令牌由五个部分组成,该部分由DOT(.)-{Header}.{EncryptionKey}.{InitializationVector}.{Ciphertext}.{AuthenticationTag}隔开。 header除其他外,还包含有关使用哪种算法来加密数据以及数据中是否有JWS的信息。 EncryptionKeyInitializationVector的两个部分包含与解密数据相关的技术数据。 Ciphertext包含加密数据。 AuthenticationTag用于确保令牌尚未更改。

如果您想了解更多有关JWS和JWE代币的信息,则可以阅读有关它的this interesting article或查看各自的RFC规格。

如何使用Spring Boot或Spring Security对JWE令牌进行解密和验证?

Spring Boot与Spring Security结合使用,包括在系统的身份验证过程中轻松验证JWS和JWE令牌的功能。

包括以下依赖关系(例如通过Maven)将功能添加到系统中。

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

有兴趣的人:春季启动框架中的配置是在以下类中完成的。

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

包括JWS令牌检查中的Spring Boot

以下春季启动配置显示了如何验证令牌:

JwsSecurityConfiguration.java
@Configuration
@EnableWebSecurity
public class JwsSecurityConfiguration {}
application.yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${url}/.well-known/jwks.json
          issuer-uri: ${url}
          audiences: resource-server-app

包括JWE令牌检查弹簧靴

要包含JWE令牌的验证,可以使用以下配置。除其他外,这需要一个私钥进行解密。在此示例中,它是RSA私钥。因此,必须为算法定义提供的私钥用于解密。这种配置主要基于弹簧安全框架的示例项目,可以找到here

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();
    }
}
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
private.key
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

使用JWE令牌测试身份验证

要测试对身份验证的配置,必须在测试中创建有效的令牌。这些JWE令牌可以使用以下类创建。内容等必须适合各自的要求。

@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();
    }
}

概括

根据我们的经验,jwe令牌经常不常用。因此,关于春季靴和弹簧安全性框架中使用情况的信息非常罕见。有了这篇博客文章,我们想让必须与JWE代币和Spring Boot合作的其他开发人员更容易。玩得开心ð


Florian Storz正在https://www.devlix.de/blog
为Devlix博客写作 本文首先在此处发表(德语):https://www.devlix.de/jwe-tokens-mit-spring-security-entschluesseln-und-verifizieren/

devlix logo

Devlix GmbH:质量,咨询,开发