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
相关产品推荐
相关产品推荐

