SAP CPI中Groovy脚本JJWT类加载/类型转换异常调试求助
SAP CPI中Groovy脚本JWT生成类加载异常的调试方案
问题背景
在SAP CPI环境开发Groovy脚本生成JWT,已上传jjwt-api-0.11.2、jjwt-impl-0.11.2、jjwt-jackson-0.11.2三个jar包。部署后首次运行正常,但后续每次调用都会抛出ClassCastException,重新部署后首次运行恢复正常,问题重复出现。此前使用Headers和Claims类时也出现相同的首次正常、后续类型转换异常的情况。
异常信息
com.sap.it.rt.adapter.http.api.exception.HttpResponseException: An internal server error occured: java.lang.ClassCastException: io.jsonwebtoken.impl.DefaultJwtBuilder (loaded by com.sap.gateway.ip.core.customdev.classloader.MergeClassLoaders@1625616) cannot be cast to io.jsonwebtoken.JwtBuilder (loaded by com.sap.gateway.ip.core.customdev.classloader.MergeClassLoaders@1624982) (found matching interface io.jsonwebtoken.JwtBuilder loaded by com.sap.gateway.ip.core.customdev.classloader.MergeClassLoaders@1625616, but needed loader com.sap.gateway.ip.core.customdev.classloader.MergeClassLoaders@1624982)@ line 70 in script1.groovy.
关键代码片段
第70行代码(两种写法均报错):
JwtBuilder jwtBuilder = Jwts.builder().setClaims(claims).setIssuedAt(nowDate).setExpiration(expDate).setHeader((Map<String, Object>) header);
DefaultJwtBuilder jwtBuilder = Jwts.builder().setClaims(claims).setIssuedAt(nowDate).setExpiration(expDate).setHeader((Map<String, Object>) header);
完整相关脚本:
import com.sap.gateway.ip.core.customdev.util.Message; import com.sap.it.api.ITApiFactory; import com.sap.it.api.keystore.KeystoreService; import java.security.cert.Certificate; import java.security.Key; import java.util.HashMap; import java.io.File; import java.io.IOException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateKeySpec; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Base64; import java.util.Date; import java.util.Map; import java.util.Scanner; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.impl.DefaultJwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.DefaultHeader; def Message processData(Message message) { def messageLog = messageLogFactory.getMessageLog(message); messageLog.addAttachmentAsString("dump_env", System.getenv().toString(), "text/plain"); Map<String, Object> header = new HashMap<String, Object>(); header.put("typ", "JWT"); header.put("alg", "RS256"); // Claims claims = Jwts.claims(); Map<String, Object> claims = new HashMap<String, Object>(); claims.put("scope", "https://www.googleapis.com/auth/devstorage.full_control"); claims.put("aud", tokenUrl); claims.put("iss", "mysecret@thing.iam.gserviceaccount.com"); /* Create JWT json with JwtBuilder */ DefaultJwtBuilder jwtBuilder = Jwts.builder().setClaims(claims).setIssuedAt(nowDate).setExpiration(expDate) .setHeader((Map<String, Object>) header); // ^^^^^^^^----> Crashing line // ...jwt signing and whatnot /* Set body and URL encode the grant_type string. */ body = "grant_type=" + URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer") + "&assertion=" + assertion message.setHeader("Content-Type", "application/x-www-form-urlencoded"); message.setBody(body); return message; }
调试工具与方法
1. 类加载信息追踪
- 添加JVM启动参数:配置
-verbose:class,输出所有类的加载来源和对应类加载器,对比首次运行与异常运行时JwtBuilder、DefaultJwtBuilder的加载器实例ID是否一致。 - 脚本内打印加载器信息:在关键位置插入代码,将类加载器信息写入CPI消息日志:
同时可以打印类的身份哈希码,确认是否为同一个类实例:def messageLog = messageLogFactory.getMessageLog(message); // 打印类加载器信息 String loaderInfo = "JwtBuilder类加载器: " + JwtBuilder.class.getClassLoader() + "\n" + "DefaultJwtBuilder类加载器: " + DefaultJwtBuilder.class.getClassLoader() + "\n" + "Jwts类加载器: " + Jwts.class.getClassLoader(); messageLog.addAttachmentAsString("class_loader_info", loaderInfo, "text/plain");loaderInfo += "\nJwtBuilder类哈希码: " + System.identityHashCode(JwtBuilder.class);
2. SAP CPI专属调试手段
- 验证类相等性:在脚本中添加判断,确认实例类与导入类是否来自同一加载器:
boolean isSameClass = Jwts.builder().getClass() == DefaultJwtBuilder.class; messageLog.addAttachmentAsString("class_equality", "类是否相等: " + isSameClass, "text/plain"); - 清理CPI缓存:部分SAP CPI租户支持在监控页面清理临时资源缓存,尝试清理后测试是否还会重复出现类加载冲突。
3. JVM类加载参数调试
- 启用类路径调试:添加JVM参数
-Dsun.misc.URLClassPath.debug=true,输出类路径的加载细节,检查是否存在重复加载JJWT jar包的情况。 - 检查类路径配置:通过
System.getProperty("java.class.path")打印当前类路径,确认是否有重复的JJWT相关jar包路径。
4. 强制类加载器测试
尝试指定类加载器加载目标类,验证是否能规避加载器冲突:
Class<?> jwtBuilderClass = Class.forName("io.jsonwebtoken.JwtBuilder", true, this.getClass().getClassLoader()); Class<?> defaultJwtBuilderClass = Class.forName("io.jsonwebtoken.impl.DefaultJwtBuilder", true, this.getClass().getClassLoader()); messageLog.addAttachmentAsString("forced_loader_info", "指定加载器的JwtBuilder: " + jwtBuilderClass.getClassLoader() + "\n" + "指定加载器的DefaultJwtBuilder: " + defaultJwtBuilderClass.getClassLoader(), "text/plain");
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

