You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CAS 6.5升级后使用Jose4J验证JWT失败问题排查

CAS 6.5.7升级后Jose4J验证JWT嵌套格式异常问题

问题背景

从CAS 5.3.15升级至6.5.7(Overlay项目),使用Jose4J验证CAS生成的JWT时出现异常。原代码在5.3版本可正常运行,现解密JWE后发现内部JWT的cty字段被设置为JWT,按规范仅外层JWT应设置该字段,怀疑CAS的JWT实现存在问题。

错误代码

// Step 1: signature validation
JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(jwtString);
jws.setKey(new AesKey(jwtConfig.getSigningKey().getBytes(StandardCharsets.UTF_8)));
jws.setAlgorithmConstraints(AlgorithmConstraints.DISALLOW_NONE);
            
if (!jws.verifySignature()) {
                logger.error(String.format("jwt have invalid signature:%s", jwtString));
                return new ValidationDTO(false, false);
}

// Step 2: check if encryption is fine, but possibly a expired token 
final byte[] decodedBytes = Base64.decodeBase64(jws.getEncodedPayload().getBytes(StandardCharsets.UTF_8));
final String decodedPayload = new String(decodedBytes, StandardCharsets.UTF_8);
final JsonWebKey jsonWebKey = JsonWebKey.Factory
                    .newJwk("\n" + "{\"kty\":\"oct\",\n" + " \"k\":\"" + jwtConfig.getEncriptionKey() + "\"\n" + "}");
            
JwtConsumer consumer = new JwtConsumerBuilder()
                    .setSkipAllValidators()
                    .setDisableRequireSignature()
                    .setSkipSignatureVerification()
                    .setDecryptionKey(new AesKey(jsonWebKey.getKey().getEncoded()))
                    .setJweAlgorithmConstraints(
                            new AlgorithmConstraints(ConstraintType.WHITELIST,
                                    KeyManagementAlgorithmIdentifiers.DIRECT))
                    
                    .setJweContentEncryptionAlgorithmConstraints(
                            new AlgorithmConstraints(ConstraintType.WHITELIST,
                                    ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256))  //this have to match CAS configuration
                    .build();
            
JwtContext context = consumer.process(decodedPayload);  // <<<<<< Exception thrown here. “Invalid JOSE Compact Serialization"

错误日志

Invalid JWT:JWT processing failed. Additional details: [[17] Unable to process nested JOSE object (cause: org.jose4j.lang.JoseException: Invalid JOSE Compact Serialization. Expecting either 3 or 5 parts for JWS or JWE respectively but was 14.): {"clientIpAddress":"127.0.0.1","sub":"test@test2121.com","authenticationDate":1659977730,"successfulAuthenticationHandlers":"careerAuthenticationHandler","iss":"https:\/\/jason.crengland.com\/cas","userAgent":"PostmanRuntime\/7.29.2","credentialType":"UsernamePasswordCredential","aud":"https:\/\/jason.crengland.com\/cas","authenticationMethod":"careerAuthenticationHandler","geoLocation":"unknown","serverIpAddress":"127.0.0.1","exp":1660006530,"iat":1659977730,"jti":"TGT-2-xxxxxxxxx-CREJDR-MBP2022"}]

org.jose4j.jwt.consumer.InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process nested JOSE object (cause: org.jose4j.lang.JoseException: Invalid JOSE Compact Serialization. Expecting either 3 or 5 parts for JWS or JWE respectively but was 14.): {"clientIpAddress":"127.0.0.1","sub":"test@test2121.com","authenticationDate":1659977730,"successfulAuthenticationHandlers":"careerAuthenticationHandler","iss":"https:\/\/jason.crengland.com\/cas","userAgent":"PostmanRuntime\/7.29.2","credentialType":"UsernamePasswordCredential","aud":"https:\/\/jason.crengland.com\/cas","authenticationMethod":"careerAuthenticationHandler","geoLocation":"unknown","serverIpAddress":"127.0.0.1","exp":1660006530,"iat":1659977730,"jti":"TGT-2-xxxxxxxx-CREJDR-MBP2022"}]
        at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:406) ~[jose4j-0.7.12.jar:na]
        at com.cre.web.security.service.JWTValidationServiceImpl.validate(JWTValidationServiceImpl.java:93) ~[classes/:na]
        at com.cre.web.security.service.JWTValidationServiceImpl$$FastClassBySpringCGLIB$$c0ab6de1.invoke(<generated>) [classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.3.2.jar:5.3.2]

    …...

        at java.lang.Thread.run(Thread.java:750) [na:1.8.0_332]
Caused by: org.jose4j.lang.JoseException: Invalid JOSE Compact Serialization. Expecting either 3 or 5 parts for JWS or JWE respectively but was 14.
        at org.jose4j.jwx.JsonWebStructure.fromCompactSerialization(JsonWebStructure.java:90) ~[jose4j-0.7.12.jar:na]
        at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:320) ~[jose4j-0.7.12.jar:na]
        ... 70 common frames omitted

问题分析

从错误日志能看到,decodedPayload已经是明文的JSON payload(并非JWE紧凑格式),但代码仍尝试用JwtConsumer处理它,导致Jose4J报错——因为它期望的是3段(JWS)或5段(JWE)的紧凑格式,而JSON字符串按.分割后会产生14段。

CAS 6.x版本调整了JWT生成逻辑:原本5.3版本中外层JWS的payload是加密的JWE(嵌套结构),但6.5版本中可能取消了嵌套,直接将明文payload放在JWS中,却错误地保留了cty=JWT的标识,导致代码误判需要解密。

解决方案

修改代码,先检查JWS的cty字段是否为JWT,同时验证decodedPayload是否为合法的JWE格式,再决定是否执行解密逻辑:

// Step 1: signature validation
JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(jwtString);
jws.setKey(new AesKey(jwtConfig.getSigningKey().getBytes(StandardCharsets.UTF_8)));
jws.setAlgorithmConstraints(AlgorithmConstraints.DISALLOW_NONE);
            
if (!jws.verifySignature()) {
    logger.error(String.format("jwt have invalid signature:%s", jwtString));
    return new ValidationDTO(false, false);
}

// Step 2: Check if payload needs decryption
String payloadContent = null;
String cty = jws.getHeaders().getStringHeaderValue("cty");
final byte[] decodedBytes = Base64.decodeBase64(jws.getEncodedPayload().getBytes(StandardCharsets.UTF_8));
final String decodedPayload = new String(decodedBytes, StandardCharsets.UTF_8);

// 判断是否为嵌套的JWE(检查格式是否是5段紧凑格式)
boolean isNestedJwe = decodedPayload.split("\\.").length == 5;

if ("JWT".equals(cty) && isNestedJwe) {
    // 需要解密的情况
    final JsonWebKey jsonWebKey = JsonWebKey.Factory
            .newJwk("\n" + "{\"kty\":\"oct\",\n" + " \"k\":\"" + jwtConfig.getEncriptionKey() + "\"\n" + "}");
    
    JwtConsumer consumer = new JwtConsumerBuilder()
            .setSkipAllValidators()
            .setDisableRequireSignature()
            .setSkipSignatureVerification()
            .setDecryptionKey(new AesKey(jsonWebKey.getKey().getEncoded()))
            .setJweAlgorithmConstraints(
                    new AlgorithmConstraints(ConstraintType.WHITELIST,
                            KeyManagementAlgorithmIdentifiers.DIRECT))
            .setJweContentEncryptionAlgorithmConstraints(
                    new AlgorithmConstraints(ConstraintType.WHITELIST,
                            ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256))
            .build();
    
    JwtContext context = consumer.process(decodedPayload);
    payloadContent = context.getJwt().getClaimsJson();
} else {
    // 直接使用明文payload
    payloadContent = decodedPayload;
}

// 后续处理payloadContent...

关于CAS是否存在bug

这个情况确实属于CAS 6.5版本JWT生成的不规范实现:按照RFC 7519,cty=JWT仅应在JWT的payload本身是另一个JWT时设置,而当前CAS在payload为明文JSON时仍设置该字段,导致客户端误判。你可以在CAS的官方issue渠道提交问题反馈。

内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 18:27:27