Json序列化对象失败的问题
背景
想要封装一个复杂的对象,然后序列化成json,通过请求工具发送Post请求。因为手动去拼接一个复杂的json很容易出现问题,所以想法是:先封装成对象,然后序列化成json。
过程与现象
网上一般推荐的方法如下:
def toJson(Object obj){
return JsonOutput.toJson(input)
}
所以我也兴致冲冲的这样写了,但是结果非常不如人意。现象是:我直接运行用groovy代码的main方法跑起来是没有问题的,正常序列化成功;但是在Jenkins pipeline环境下的时候就总是会序列化成一个空对象(即总是返回:{})。
于是在网上搜索各种资料,最后发现很少类似的资料,最后在询问ChatGPT才找到一些关键的线索,ChatGPT回复如下:
总的来说就是get/set方法可能会产生的是动态属性,动态属性在Pipeline中是没法序列化了,所以肯定就是空对象了。
按照这个线索,我不停的测试发现了问题的原因。结论就是:在groovy类定义中如果我们手动重写了get/set方法就会导致对象序列化失败。可能重写了get/set方法在groovy中就是动态属性。
结论与解决办法
在groovy类定义中如果我们不要重写了get/set方法,只要定义属性就好了,groovy定义了属性就自动会有get/set方法。
在测试过程中也发现了几个关于类定义需要注意的点,如下:
- 不要写内部静态类,写内部静态类,可能会发生new对象的时候,找不到类信息;
- 不要将类写在一个文件里面,不然也会发生找不到类的情况,总的来说就是一个类一个文件;
- 定义的类最好是实现Serializable接口,否则也可能在脚本传递中报没法序列化的错误;
- 在pipeline里面最好是类似直接写json的方式去定义一个复杂对象,因为脚本化,基本类的复用性不是很强,注意在groovy中是[]包裹不是{}包裹。类定义如下:
def newPostJson(timestamp, sign, title, author, dateStr, projectName, env,
buildNumber, jobUrl, detailLog) {
def testBody = [timestamp: timestamp,
sign : sign,
msg_type : "interactive",
card : [
type: "template",
data: [
template_id : "ctp_AAumzLoL5YIf",
template_variable: [
title : title,
author : author,
time : dateStr,
projectName: projectName,
env : env,
buildNumber: buildNumber,
projectUrl : jobUrl,
content : detailLog
]
]
]]
return JsonOutput.toJson(testBody)
}
空数组定义和Java的有区别,导致报错的问题
背景
请求接口的时候,有些接口需要做签名,并且需要指定sha256算法签名。
过程与现象
很自然就知道百度,google代码;基本代码如下:
private static def sha256(String body, String secret) {
//使用HmacSHA256算法计算签名
Mac mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
byte[] signData = mac.doFinal(body.getBytes(StandardCharsets.UTF_8))
return new String(Base64.getEncoder().encode(signData))
}
但是这次刚刚好签名的内容比较特殊,对一个空字节数组做签名,代码如下:
private static def feishuSign(String secret) {
String stringToSign = (System.currentTimeMillis() / 1000) + "/n" + secret
//使用HmacSHA256算法计算签名
Mac mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
byte[] signData = mac.doFinal(new byte[]{})
return new String(Base64.getEncoder().encode(signData))
}
现象就是:代码编辑器不会报错,但是运行的时候就会报错。 错误如下截图:
然后我将代码转换成java代码,直接运行也是不会报错的。
结论与解决办法
后来尝试了很多次,我后来想到直接定义一个0元素数组是否跟上面定义一致呢?于是在java测试了一下,结果是一致的。最后代码改动如下:
private static def feishuSign(String secret) {
String stringToSign = (System.currentTimeMillis() / 1000) + "/n" + secret
//使用HmacSHA256算法计算签名
Mac mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
byte[] signData = mac.doFinal(new byte[0])
return new String(Base64.getEncoder().encode(signData))
}
就不会报错了,并且结果也是正确的。
CPS的问题
就是有些方法,可能不安全,就会被jenkins的sandbox拦截不让执行,一般错误描述如下:
Scripts not permitted to use staticMethod groovy.json.JsonOutput toJson java.lang.Object. Administrators can decide whether to approve or reject this signature.
[Pipeline] }
[Pipeline] // script
Post stage
[Pipeline] script
[Pipeline] {
[Pipeline] httpRequest
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod groovy.json.JsonOutput toJson java.lang.Object
at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectStaticMethod(StaticWhitelist.java:243)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onStaticCall(SandboxInterceptor.java:212)
at org.kohsuke.groovy.sandbox.impl.Checker$2.call(Checker.java:214)
at org.kohsuke.groovy.sandbox.impl.Checker.checkedStaticCall(Checker.java:218)
at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:120)
at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
at WorkflowScript.run(WorkflowScript:73)
at ___cps.transform___(Native Method)
at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:90)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:113)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:83)
at sun.reflect.GeneratedMethodAccessor366.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.get(LocalVariableBlock.java:38)
at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
at com.cloudbees.groovy.cps.impl.LocalVariableBlock.evalLValue(LocalVariableBlock.java:27)
at com.cloudbees.groovy.cps.LValueBlock$BlockImpl.eval(LValueBlock.java:55)
at com.cloudbees.groovy.cps.LValueBlock.eval(LValueBlock.java:16)
at com.cloudbees.groovy.cps.Next.step(Next.java:83)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:177)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:166)
at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:166)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:51)
at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:187)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:420)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$400(CpsThreadGroup.java:95)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:330)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:294)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
Finished: FAILURE
解决办法
方法一
按照提示点击即可,操作方法如截图:
到里面页面同意申请即可
本人这个已经同意过了,所以没有通过申请的按钮。
方法二
对调用的方法打上注解:@NonCPS
,例如:
@NonCPS
static def toJson(Object input) {
JsonOutput.toJson(input)
}
方法三
手动去系统添加执行权限,操作方法如截图:
去里面提交申请执行即可