
外卖CPS系统中Java后端服务的接口加密与数据脱敏实现技巧在外卖CPS系统中涉及用户手机号、银行卡号、佣金明细等敏感数据需在传输和存储环节实施强加密与动态脱敏。本文结合AES/GCM对称加密、RSA非对称密钥交换、Jackson序列化脱敏、Spring AOP拦截等技术提供端到端的安全防护方案。1. 请求/响应体自动加解密基于Spring MVC拦截自定义RequestBodyAdvice和ResponseBodyAdvicepackagebaodanbao.com.cn.cps.security;importorg.springframework.core.MethodParameter;importorg.springframework.http.HttpInputMessage;importorg.springframework.http.converter.HttpMessageConverter;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;importjava.io.ByteArrayInputStream;importjava.lang.reflect.Type;ControllerAdvicepublicclassCryptoResponseBodyAdviceimplementsResponseBodyAdviceObject{Overridepublicbooleansupports(MethodParameterreturnType,Class?extendsHttpMessageConverter?converterType){returntrue;}OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,TypeselectedContentType,Class?extendsHttpMessageConverter?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){if(bodyinstanceofEncryptable){StringjsonJSON.toJSONString(body);StringencryptedAesGcmUtil.encrypt(json,getSecretKeyFromHeader(request));returnnewEncryptedResponse(encrypted);}returnbody;}}// 请求解密publicclassCryptoRequestBodyAdviceextendsRequestBodyAdviceAdapter{Overridepublicbooleansupports(MethodParametermethodParameter,TypetargetType,Class?extendsHttpMessageConverter?converterType){returntrue;}OverridepublicHttpInputMessagebeforeBodyRead(HttpInputMessageinputMessage,MethodParameterparameter,TypetargetType,Class?extendsHttpMessageConverter?converterType)throwsIOException{if(isEncryptedRequest(parameter)){StringencryptedStreamUtils.copyToString(inputMessage.getBody(),StandardCharsets.UTF_8);StringsecretKeygetSecretKeyFromHeader(inputMessage.getHeaders());StringplainJsonAesGcmUtil.decrypt(encrypted,secretKey);returnnewHttpInputMessage(){OverridepublicInputStreamgetBody(){returnnewByteArrayInputStream(plainJson.getBytes(StandardCharsets.UTF_8));}OverridepublicHttpHeadersgetHeaders(){returninputMessage.getHeaders();}};}returninputMessage;}}2. AES-GCM加解密工具类认证加密publicclassAesGcmUtil{privatestaticfinalintGCM_IV_LENGTH12;privatestaticfinalintGCM_TAG_LENGTH16;publicstaticStringencrypt(Stringplaintext,Stringbase64Key){try{byte[]keyBytesBase64.getDecoder().decode(base64Key);SecretKeykeynewSecretKeySpec(keyBytes,AES);CiphercipherCipher.getInstance(AES/GCM/NoPadding);GCMParameterSpecspecnewGCMParameterSpec(GCM_TAG_LENGTH*8,generateIv());cipher.init(Cipher.ENCRYPT_MODE,key,spec);byte[]ciphertextcipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));byte[]outputnewbyte[GCM_IV_LENGTHciphertext.length];System.arraycopy(spec.getIV(),0,output,0,GCM_IV_LENGTH);System.arraycopy(ciphertext,0,output,GCM_IV_LENGTH,ciphertext.length);returnBase64.getEncoder().encodeToString(output);}catch(Exceptione){thrownewRuntimeException(Encrypt failed,e);}}publicstaticStringdecrypt(StringencryptedBase64,Stringbase64Key){try{byte[]keyBytesBase64.getDecoder().decode(base64Key);byte[]dataBase64.getDecoder().decode(encryptedBase64);byte[]ivArrays.copyOf(data,GCM_IV_LENGTH);byte[]ciphertextArrays.copyOfRange(data,GCM_IV_LENGTH,data.length);SecretKeykeynewSecretKeySpec(keyBytes,AES);CiphercipherCipher.getInstance(AES/GCM/NoPadding);GCMParameterSpecspecnewGCMParameterSpec(GCM_TAG_LENGTH*8,iv);cipher.init(Cipher.DECRYPT_MODE,key,spec);byte[]plaintextcipher.doFinal(ciphertext);returnnewString(plaintext,StandardCharsets.UTF_8);}catch(Exceptione){thrownewRuntimeException(Decrypt failed,e);}}privatestaticbyte[]generateIv(){byte[]ivnewbyte[GCM_IV_LENGTH];newSecureRandom().nextBytes(iv);returniv;}}3. 敏感字段动态脱敏基于Jackson注解自定义Sensitive注解与序列化器Retention(RetentionPolicy.RUNTIME)JacksonAnnotationsInsideJsonSerialize(usingSensitiveDataSerializer.class)publicinterfaceSensitive{SensitiveTypevalue()defaultSensitiveType.MOBILE;}publicenumSensitiveType{MOBILE,ID_CARD,BANK_CARD}publicclassSensitiveDataSerializerextendsJsonSerializerString{Overridepublicvoidserialize(Stringvalue,JsonGeneratorgen,SerializerProviderserializers)throwsIOException{if(valuenull){gen.writeNull();return;}SensitiveTypetypefindSensitiveType(serializers);Stringmaskedswitch(type){caseMOBILE-value.replaceAll((\\d{3})\\d{4}(\\d{4}),$1****$2);caseID_CARD-value.replaceAll((\\d{6})\\d{8}(\\d{4}),$1********$2);caseBANK_CARD-value.replaceAll((\\d{4})\\d{8}(\\d{4}),$1********$2);default-value;};gen.writeString(masked);}privateSensitiveTypefindSensitiveType(SerializerProviderprovider){// 从上下文或字段元数据获取类型简化处理returnSensitiveType.MOBILE;}}使用示例publicclassUserInfo{privateStringname;Sensitive(SensitiveType.MOBILE)privateStringphone;Sensitive(SensitiveType.ID_CARD)privateStringidCard;}4. 数据库存储加密MyBatis TypeHandler对银行卡号等字段透明加解密MappedJdbcTypes(JdbcType.VARCHAR)publicclassEncryptedStringTypeHandlerextendsBaseTypeHandlerString{OverridepublicvoidsetNonNullParameter(PreparedStatementps,inti,Stringparameter,JdbcTypejdbcType)throwsSQLException{StringencryptedAesGcmUtil.encrypt(parameter,getKeyFromConfig());ps.setString(i,encrypted);}OverridepublicStringgetNullableResult(ResultSetrs,StringcolumnName)throwsSQLException{Stringencryptedrs.getString(columnName);returnencrypted!null?AesGcmUtil.decrypt(encrypted,getKeyFromConfig()):null;}privateStringgetKeyFromConfig(){returnbaodanbao.com.cn.cps.config.SecurityConfig.getDbEncryptKey();}}Mapper中指定resultcolumnbank_cardpropertybankCardtypeHandlerbaodanbao.com.cn.cps.security.EncryptedStringTypeHandler/5. 密钥安全管理禁止硬编码通过KMS或配置中心动态获取ComponentpublicclassKeyManager{privatefinalStringkmsUrl;privatevolatileStringcachedKey;privatefinalScheduledExecutorServiceschedulerExecutors.newSingleThreadScheduledExecutor();PostConstructpublicvoidinit(){refreshKey();scheduler.scheduleAtFixedRate(this::refreshKey,1,1,TimeUnit.HOURS);}privatevoidrefreshKey(){ResponseEntityStringresprestTemplate.getForEntity(kmsUrl/key/cps-data,String.class);this.cachedKeyresp.getBody();// Base64格式}publicStringgetCurrentKey(){returncachedKey;}}本文著作权归 俱美开放平台 转载请注明出处