導言
不知道大家在網(wǎng)上購物的時候嚷狞,有沒有這樣的念頭块促,如果能把未付款的訂單偷偷用一條SQL改成已付款,該多么美好啊床未。那么在實際開發(fā)過程中竭翠,我們應當如何保證數(shù)據(jù)庫里的數(shù)據(jù)在保存后不會被偷偷更改?
大家好我是日暮與星辰之間薇搁,創(chuàng)作不易斋扰,如果覺得有用,求點贊啃洋,求收藏传货,求轉發(fā),謝謝宏娄。
理論
在介紹具體的內容之間问裕,先介紹MD5算法,簡單的來說孵坚,MD5能把任意大小粮宛、長度的數(shù)據(jù)轉換成固定長度的一串字符,經常玩大型游戲的朋友應該都注意到過卖宠,各種補丁包窟勃、端游客戶端之類的大型文件一般都附有一個MD5值,用于確保你下載文件的完整性逗堵。那么在這里秉氧,我們可以借鑒其思想,對訂單的某些屬性進行加密計算蜒秤,得出來一個 MD5值一并保存在數(shù)據(jù)庫當中汁咏。從數(shù)據(jù)庫取出數(shù)據(jù)后第一時間進行校驗,如果有異常更改作媚,那么及時拋出異常進行人工處理攘滩。
實現(xiàn)
道理我都懂,但是我要如何做呢纸泡,別急漂问,且聽我一一道來。
這種需求聽起來并不強綁定于某個具體的業(yè)務需求女揭,這就要用到了我們熟悉的鼎鼎有名的AOP(面向切面編程)來實現(xiàn)蚤假。
首先定義四個類型的注解作為AOP的切入點。@Sign
和@Validate
都是作用在方法層面的吧兔,分別用于對方法的入?yún)⑦M行加簽和驗證方法的返回值的簽名磷仰。@SignField
用于注解關鍵的不容篡改的字段。@ValidateField
用于注解保存計算后得出的簽名值境蔼。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sign {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignField {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidField {
}
以訂單的實體為例 sn,amt,status,userId就是關鍵字段灶平,絕不能允許有人在落單到數(shù)據(jù)庫后對這些字段偷偷篡改伺通。
public class Order {
@SignField
private String sn;
@SignField
private String amt;
@SignField
private int status;
@SignField
private int userId;
@ValidField
private String sign;
}
下面就到了重頭戲的部分,如何通過AOP來進行實現(xiàn)逢享。
1. 定義切入點
@Pointcut("execution(@com.example.demo.annotations.Sign * *(..))")
public void signPointCut() {
}
@Pointcut("execution(@com.example.demo.annotations.Validate * *(..))")
public void validatePointCut() {
}
2.環(huán)繞切入點
@Around("signPointCut()")
public Object signAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (Object o : args) {
System.out.println(o);
sign(o);
}
Object res = pjp.proceed(args);
return res;
}
@Around("validatePointCut()")
public Object validateAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
Object res = pjp.proceed(args);
valid(res);
return res;
}
3. 簽名的實現(xiàn)
1.獲取需要簽名字段
private Map<String, String> getSignMap(Object o) throws IllegalAccessException {
Map<String, String> fieldNameToValue = new HashMap<>();
for (Field f : o.getClass().getDeclaredFields()) {
System.out.println(f.getName());
for (Annotation annotation : f.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(SignField.class)) {
String value = "";
f.setAccessible(true);
fieldNameToValue.put(f.getName(), f.get(o).toString());
}
}
}
return fieldNameToValue;
}
2.計算出簽名值罐监,這里在屬性名和屬性值以外加入了我的昵稱以防止他人猜測,同時使用了自定義的分隔符來加強密碼強度瞒爬。
private String getSign(Map<String, String> fieldNameToValue) {
List<String> names = new ArrayList<>(fieldNameToValue.keySet());
StringBuilder sb = new StringBuilder();
for (String name : names)
sb.append(name).append("@").append(fieldNameToValue.get(name));
System.out.println(sb.append("日暮與星辰之間").toString());
String signValue = DigestUtils.md5DigestAsHex(sb.toString().getBytes(StandardCharsets.UTF_8));
return signValue;
}
- 找到保存簽名的字段
private Field getValidateFiled(Object o) {
for (Field f : o.getClass().getDeclaredFields()) {
for (Annotation annotation : f.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(ValidField.class)) {
return f;
}
}
}
return null;
}
- 對保存簽名的字段進行賦值
public void sign(Object o) throws IllegalAccessException {
Map<String, String> fieldNameToValue = getSignMap(o);
if (fieldNameToValue.isEmpty()) {
return;
}
Field validateField = getValidateFiled(o);
if (validateField == null)
return;
String signValue = getSign(fieldNameToValue);
validateField.setAccessible(true);
validateField.set(o, signValue);
}
- 對從數(shù)據(jù)庫中取出的對象進行驗證
public void valid(Object o) throws IllegalAccessException {
Map<String, String> fieldNameToValue = getSignMap(o);
if (fieldNameToValue.isEmpty()) {
return;
}
Field validateField = getValidateFiled(o);
validateField.setAccessible(true);
String signValue = getSign(fieldNameToValue);
if (!Objects.equals(signValue, validateField.get(o))) {
throw new RuntimeException("數(shù)據(jù)非法");
}
}
使用示例
對將要保存到數(shù)據(jù)庫的對象進行簽名
@Sign
public Order save( Order order){
orderList.add(order);
return order;
}
驗證從數(shù)據(jù)庫中取出的對象是否合理
@Validate
public Order query(@ String sn){
return orderList.stream().filter(e -> e.getSn().equals(sn)).findFirst().orElse(null);
}
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布笑诅!