由于当今的要求,所有暴露于大量请求的系统都必须在短时间内做出响应。因此,重要的是,要处理请求的单个步骤尽可能快,安全。因此,例如,将无状态协议(例如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的信息。 EncryptionKey
和InitializationVector
的两个部分包含与解密数据相关的技术数据。 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/