
接着上一话JWE:安全传输敏感数据的最佳实践(上),我们已经实现了接收密文,并进行了解密,但是这只是单方向的,如果我们想返回加密数据给客户端,应该怎么做???这时就需要用客户端的公钥加密数据,并返回了......
接着上一话JWE:安全传输敏感数据的最佳实践(上),我们已经实现了接收密文,并进行了解密,但是这只是单方向的,如果我们想返回加密数据给客户端,应该怎么做???这时就需要用客户端的公钥加密数据,并返回了。但是我们服务器如果支持多个客户端,我们又怎样去根据客户端的不同,读取对应的公钥加密数据返回???接下来我们一步步去解决。(PS:接下来可能会接触到一些源码部分,可能比较晦涩难懂,我们不需要搞懂每一行代码,我们要站上帝的视角,去找出我们想要的东西,并加以利用)


就像这样,我们首先将客户端传过来的数据进行解密,然后自己手动赋值到对应的参数上,每次都写这样的加解密过程,未免有些不妥,我们想做到如图上的效果,自动赋值到controller的参数,我们直接请求一下,看结果怎样?

好吧,springmvc根本不认识,你说它是json格式,它就用json的格式去解析,但根本解析不了

我们看下后端的控制台的报错信息,有一个警告。我们试着去打开这个类,并发现最终的报错信息是在这里,


试着在这里打一个断点,从调用堆栈中,寻找一下有没有什么突破点,我们分析下,在processDispatchResult之后的方法,都在怎么包装返回错误信息,我们就需要debug进processDispatchResult方法,在报错之前看,能不能找到什么有用信息


看这里,如果异常不为空,就直接走进异常的处理流程,我们就要返回去,看processDispatchResult之前的doDispatch方法

我们从这个方法一直跟进去,直到这个方法,从函数名称,我们大概可以猜出,这是将我们requestbody转换成对应controller上的参数

我们再往下走,来到这个循环里,可以看到,springmvc默认有多个转换器

然后因为我们的content-type传的是application/json,MappingJackson2HttpMessageConverter这个转换器就尝试解释我们的requestbody,很遗憾,转换失败

到这里,我们就有思路了,既然是因为没有合适的转换器去自动适配我们的数据,我们就应该造一个出来去自动适配我们的数据格式。
好了,下面就是代码的环节,把主要代码都贴出来,篇幅有限。
这个是jwe的加解密器,负责jwe的加解密
;*;;;;;;/***@authorsakka*@*@description:jwe加解密器*@date2023/3/30*/publicclassJweRSAEncryptionDecryption{publicstaticStringencrypt(byte[]payload,PublicKeypublicKey)throwsException{//创建加密器JWEHeaderheader=(_OAEP_256,).build();JWEEncrypterjweEncrypter=newRSAEncrypter((RSAPublicKey)publicKey);//加密JSON数据PayloadjwePayload=newPayload(payload);JWEObjectjweObject=newJWEObject(header,jwePayload);(jweEncrypter);//将JWE对象转换为JWE字符串();}publicstaticbyte[]decrypt(Stringjwe,PrivateKeyprivateKey)throwsException{//创建解密器JWEDecrypterjweDecrypter=newRSADecrypter(privateKey);//解密JWE字符串JWEObjectjweObject=(jwe);(jweDecrypter);//将解密后的JSON数据转换为JSONObject对象Payloadpayload=();();}}重点在JweMappingJackson2HttpMessageConverter这个类,canRead判断是否对body进行转换,read读取body内容并进行转换,我们无需完全重新实现对json的解析转换,我们只需把密文进行解密,并传回父类AbstractJackson2HttpMessageConverter去解析就好了。也没有太多复杂的逻辑。
;;;;;;;;;;;;;;;;;;;;;;;;;;/***@authorsakka*@*@description:JweMappingJackson2HttpMessageConverter*@date2023/3/30*/publicclassJweMappingJackson2HttpMessageConverterextsAbstractJackson2HttpMessageConverter{/***配置文件*/;/***服务器私钥*/privatePrivateKeyserverPrivateKey;/***客户端公钥*/privatefinalMapString,PublicKeyclientPublicKeys;publicJweMappingJackson2HttpMessageConverter(){this(jweProperties,().build());}publicJweMappingJackson2HttpMessageConverter(,ObjectMapperobjectMapper){super(objectMapper,_JSON,newMediaType("application","*+json"));=jweProperties;clientPublicKeys=newHashMap();init();}privatevoidinit(){try{PKCS8EncodedKeySpeckeySpec=newPKCS8EncodedKeySpec((()));KeyFactorykeyFactory=("RSA");serverPrivateKey=(keySpec);for(:()){StringclientId=();PublicKeypublicKey=getPublicKey(());(clientId,publicKey);}}catch(Exceptione){thrownewJweException(e);}}/***String转公钥PublicKey**@paramkeybase64*@return公钥*@throwsException异常*/protectedPublicKeygetPublicKey(Stringkey)throwsException{byte[]keyBytes=(key);X509EncodedKeySpeckeySpec=newX509EncodedKeySpec(keyBytes);KeyFactorykeyFactory=("RSA");(keySpec);}@OverridepublicbooleancanRead(Typetype,Class?contextClass,MediaTypemediaType){(type,contextClass,mediaType)isJweEntity(type,contextClass);}protectedbooleanisJweEntity(Typetype,Class?contextClass){JavaTypejavaType=getJavaType(type,contextClass);().getDeclaredAnnotation()!=null;}@OverridepublicObjectread(Typetype,Class?contextClass,HttpInputMessageinputMessage)throwsIOException,HttpMessageNotReadableException{HttpHeadersheaders=();MediaTypecontentType=();Charsetcharset=getCharset(contentType);InputStreaminputStream=(());Stringjwe=(inputStream,charset);if((jwe)){thrownewHttpMessageNotReadableException("I/Oerrorwhilereadinginputmessage",inputMessage);}try{byte[]decrypt=(jwe,serverPrivateKey);(type,contextClass,newHttpInputMessage(){@OverridepublicInputStreamgetBody(){returnnewByteArrayInputStream(decrypt);}@OverridepublicHttpHeadersgetHeaders(){returnheaders;}});}catch(Exceptione){thrownewJweException(e);}}}这个注解主要是为了标记类是否需要进行解密,是配合上面的JweMappingJackson2HttpMessageConverter
使用。
;;;*;;/***@authorsakka*@*@description://TODO*@date2023/3/30*/@Target({TYPE})@Retention(RUNTIME)public@interfaceJweEntity{}

这个配置主要是把我们配置文件里的公钥私钥都加载进来,并初始化JweMappingJackson2HttpMessageConverter
;;;;;;;;/***@authorsakka*@*@description://TODO*@date2023/3/30*/@ConfigurationpublicclassJweConfig{@BeanpublicJweMappingJackson2HttpMessageConverterjweMappingJackson2HttpMessageConverter(@AutowiredJwePropertiesjweProperties){returnnewJweMappingJackson2HttpMessageConverter(jweProperties);}@ConfigurationProperties(prefix="jwe")@BeanpublicJwePropertiesjweProperties(){returnnewJweProperties();}@DatapublicstaticclassJweProperties{privateStringserverPrivateKey;privateListClientPublicKeyclientPublicKeys;}@DatapublicstaticclassClientPublicKey{privateStringclientId;privateStringclientKey;}}jwe:server-privateKey:MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCyoFNg6oFTmt2l1Ff3+jDGLScezGzIav/w9ZBnwa09CKVd825AQVY7TWjRlTwCCf7DNq2kYI7AiBJoQXinq4ji3KELwGWeRBk38ZyNuvKWPMitGW7w0qwu7jRJA7cJhr6nsMDfql1f+cxD1Bx5J4Zb17sTlha6IyFQOCpZUR2ZHFf5vF+g1ikG02W/LF4vQwoQhRWWKRgp75TEqGI9FzCMEiIcp+xjY+zM/iHjzlSNwha97ImMG7ycSlSAdSpQd0DWebfZaZQxw6FNTjW1fpmhgrqpQF41fjGd84buOcTvpXPZByxVOedOpND/3efEQitmxJ9D+93OLwuvJjbFLQSZAgMBAAECggEAJHg4XcazPeMWEufuR/pkX+nTHWYmZar283bnk0+HM7lirfJoFaVhWj09Q+EgvdfVlHzC6hcuvh9qBrArVqxeh9b86H3RIYWM0o+5Y3SCV+s0G6dgL7oLno9SzH9+LOs+XNVpI6FQbCp/qm+RmqjXtUOv9dlEbZ+DizHUb6TwkpRPMo3BHPUKGpajmP0pgSkQz8x4MWJ0a6SVST+/E7ZbwF8PobzOQCxND2yZQah/TY1EvCCyOM6chGm0loCyC4HGTBmDm/0LcWRqgiI8GiglEijGu2Iha6UR11JaEStHoc1Sy38Orj377bhndm2l19HNOQIslp9m0VP4WUSG3GJDKwKBgQC55+HY29F0H2jy7p94+snPCKVG0uJHu0IKhFN09fChP/1q0PLJuz3Vj07YUjGneSL3RHM1D+HbMYAGnZ+7GFmwIi8FLKBVZDx4L7ENhcPMUM2q0rsBphZuHfD14Hf9I0xUiNrpyTLuVPJfK3kzx2NYQmRGo6tY+INDuI5L2HEELwKBgQD1+c4ilEQtTRvoIa+BfZ/oOBeFaYsQGlLL+8yVbsPsfH+0MMoiIY++my5rDhT//8QXNhnOI1cs7CFKELA/99Mk7y9G/4o+cMb1kAyZJU6zNoSe9Eitdyo0qQQ20NVAW+YWX0zAuAm5bttk1RvVGz/wiuOotVw6oCSR0uUYUWCptwKBgB2psy6f/G6z6FIC4y0xjuva7Ew9r99UMLhu3sYly+xewne9uU+Y8cfWovT/QG8BdCPSJzPLQfVwk4X6tpbqzry855XCxh557PAcY/rNYi2Cox5jm3Uq5B9T5bPFyj9412ARqiRtdxPyN+4ZiLBLWz2k8k0XJmr+1CsFEqdldLr/AoGAEjyqLuAlSeKMriJJO+WPhI0cGVUg7Vm2R89sdKvYtODqKvbvFaa9XJlu0JsjrXNOG5Z0RVdTcE41jaM9HhEGw5dEPxRVMJn19mDuvjAI7LqfDJX6CXprU6owWMwU84ecwI3iR+udNPVmKMywGpXBoNj7VhfUNbiH3ZPwTmRCMXMCgYBi5I1KJ7kyaHKTilJEAhiYv6XBwsJScJkdAXWuA/SdG3aWQAEc4SOrRwqmqbHWYOm827tb20kG09rHnVS+tVSShvCkv4OcAr2X0L04IX9OfvUqI5pPWiQd/VzCQrTPcelixgkUkG4Sc2dRr6gvvFOKFAAVTQrePh9clk8kd3bg+g==client-publicKeys:-client-id:testclient-key:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsqBTYOqBU5rdpdRX9/owxi0nHsxsyGr/8PWQZ8GtPQilXfNuQEFWO01o0ZU8Agn+wzatpGCOwIgSaEF4p6uI4tyhC8BlnkQZN/GcjbryljzIrRlu8NKsLu40SQO3CYa+p7DA36pdX/nMQ9QceSeGW9e7E5YWuiMhUDgqWVEdmRxX+bxfoNYpBtNlvyxeL0MKEIUVlikYKe+UxKhiPRcwjBIiHKfsY2PszP4h485UjcIWveyJjBu8nEpUgHUqUHdA1nm32WmUMcOhTU41tX6ZoYK6qUBeNX4xnfOG7jnE76Vz2QcsVTnnTqTQ/93nxEIrZsSfQ/vdzi8LryY2xS0EmQIDAQAB
好了,接下来就是测试环节,是骡子是马就是这一刻!!!



