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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 05:45:05