外卖CPS系统中Java后端服务的接口加密与数据脱敏实现技巧
在外卖CPS系统中,涉及用户手机号、银行卡号、佣金明细等敏感数据,需在传输和存储环节实施强加密与动态脱敏。本文结合AES/GCM对称加密、RSA非对称密钥交换、Jackson序列化脱敏、Spring AOP拦截等技术,提供端到端的安全防护方案。本文著作权归 俱美开放平台 ,转载请注明出处!
·
外卖CPS系统中Java后端服务的接口加密与数据脱敏实现技巧
在外卖CPS系统中,涉及用户手机号、银行卡号、佣金明细等敏感数据,需在传输和存储环节实施强加密与动态脱敏。本文结合AES/GCM对称加密、RSA非对称密钥交换、Jackson序列化脱敏、Spring AOP拦截等技术,提供端到端的安全防护方案。
1. 请求/响应体自动加解密(基于Spring MVC拦截)
自定义RequestBodyAdvice和ResponseBodyAdvice:
package baodanbao.com.cn.cps.security;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Type;
@ControllerAdvice
public class CryptoResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
Type selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Encryptable) {
String json = JSON.toJSONString(body);
String encrypted = AesGcmUtil.encrypt(json, getSecretKeyFromHeader(request));
return new EncryptedResponse(encrypted);
}
return body;
}
}
// 请求解密
public class CryptoRequestBodyAdvice extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
if (isEncryptedRequest(parameter)) {
String encrypted = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
String secretKey = getSecretKeyFromHeader(inputMessage.getHeaders());
String plainJson = AesGcmUtil.decrypt(encrypted, secretKey);
return new HttpInputMessage() {
@Override
public InputStream getBody() {
return new ByteArrayInputStream(plainJson.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
return inputMessage;
}
}

2. AES-GCM加解密工具类(认证加密)
public class AesGcmUtil {
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 16;
public static String encrypt(String plaintext, String base64Key) {
try {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, generateIv());
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
byte[] output = new byte[GCM_IV_LENGTH + ciphertext.length];
System.arraycopy(spec.getIV(), 0, output, 0, GCM_IV_LENGTH);
System.arraycopy(ciphertext, 0, output, GCM_IV_LENGTH, ciphertext.length);
return Base64.getEncoder().encodeToString(output);
} catch (Exception e) {
throw new RuntimeException("Encrypt failed", e);
}
}
public static String decrypt(String encryptedBase64, String base64Key) {
try {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
byte[] data = Base64.getDecoder().decode(encryptedBase64);
byte[] iv = Arrays.copyOf(data, GCM_IV_LENGTH);
byte[] ciphertext = Arrays.copyOfRange(data, GCM_IV_LENGTH, data.length);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decrypt failed", e);
}
}
private static byte[] generateIv() {
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
return iv;
}
}
3. 敏感字段动态脱敏(基于Jackson注解)
自定义@Sensitive注解与序列化器:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveDataSerializer.class)
public @interface Sensitive {
SensitiveType value() default SensitiveType.MOBILE;
}
public enum SensitiveType {
MOBILE, ID_CARD, BANK_CARD
}
public class SensitiveDataSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
SensitiveType type = findSensitiveType(serializers);
String masked = switch (type) {
case MOBILE -> value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
case ID_CARD -> value.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
case BANK_CARD -> value.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1********$2");
default -> value;
};
gen.writeString(masked);
}
private SensitiveType findSensitiveType(SerializerProvider provider) {
// 从上下文或字段元数据获取类型,简化处理
return SensitiveType.MOBILE;
}
}
使用示例:
public class UserInfo {
private String name;
@Sensitive(SensitiveType.MOBILE)
private String phone;
@Sensitive(SensitiveType.ID_CARD)
private String idCard;
}
4. 数据库存储加密(MyBatis TypeHandler)
对银行卡号等字段透明加解密:
@MappedJdbcTypes(JdbcType.VARCHAR)
public class EncryptedStringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
String encrypted = AesGcmUtil.encrypt(parameter, getKeyFromConfig());
ps.setString(i, encrypted);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String encrypted = rs.getString(columnName);
return encrypted != null ? AesGcmUtil.decrypt(encrypted, getKeyFromConfig()) : null;
}
private String getKeyFromConfig() {
return baodanbao.com.cn.cps.config.SecurityConfig.getDbEncryptKey();
}
}
Mapper中指定:
<result column="bank_card" property="bankCard" typeHandler="baodanbao.com.cn.cps.security.EncryptedStringTypeHandler"/>
5. 密钥安全管理
禁止硬编码,通过KMS或配置中心动态获取:
@Component
public class KeyManager {
private final String kmsUrl;
private volatile String cachedKey;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@PostConstruct
public void init() {
refreshKey();
scheduler.scheduleAtFixedRate(this::refreshKey, 1, 1, TimeUnit.HOURS);
}
private void refreshKey() {
ResponseEntity<String> resp = restTemplate.getForEntity(kmsUrl + "/key/cps-data", String.class);
this.cachedKey = resp.getBody(); // Base64格式
}
public String getCurrentKey() {
return cachedKey;
}
}
本文著作权归 俱美开放平台 ,转载请注明出处!
更多推荐
所有评论(0)