自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

保護敏感數(shù)據(jù):Spring Boot中敏感字段加密解密的高效解決方案

安全 應用安全
敏感數(shù)據(jù)是指那些泄漏后可能會給社會或個人造成嚴重危害的數(shù)據(jù),以個人隱私信息為例,如手機號碼、家庭住址、郵箱、身份證號、銀行卡帳號、購物網站的支付密碼、登陸密碼等等。另外從社會的角度出發(fā),也有很多數(shù)據(jù)是屬于敏感數(shù)據(jù),如:居民的生物基因信息等等。

前言

相信大家都有這樣一個煩惱,就是經常會接到各種推銷、廣告的電話和短信,如果你沒有在他那里留下過聯(lián)系方式,他又是如何得到了你的聯(lián)系方式呢?毫無疑問,是個人信息被泄漏了。個人信息的泄漏有人為不合法謀利的因素,也有系統(tǒng)不合理的安全設計造成泄漏的因素。當然系統(tǒng)設計的角度出發(fā),敏感信息需要加密存儲的,數(shù)據(jù)展示的時候也要進行相應的脫敏處理,但是從一些關于個信息泄漏的新聞報道來看,有好多的網站后臺竟然是“裸奔”狀態(tài),簡直太可怕了。其實敏感數(shù)據(jù)的處理也不復雜,說到底是安全意識不強。當然,這篇文章和大家分享的重點是加密和解密的方法,不是數(shù)據(jù)安全的重要性。

基本概念

敏感數(shù)據(jù)

敏感數(shù)據(jù)是指那些泄漏后可能會給社會或個人造成嚴重危害的數(shù)據(jù),以個人隱私信息為例,如手機號碼、家庭住址、郵箱、身份證號、銀行卡帳號、購物網站的支付密碼、登陸密碼等等。另外從社會的角度出發(fā),也有很多數(shù)據(jù)是屬于敏感數(shù)據(jù),如:居民的生物基因信息等等。

數(shù)據(jù)加密

數(shù)據(jù)加密是指對數(shù)據(jù)重新編碼來保護數(shù)據(jù),獲取實際數(shù)據(jù)的唯一辦法就是使用密鑰解密數(shù)據(jù);

數(shù)據(jù)解密

數(shù)據(jù)解密與數(shù)據(jù)加密是相對的,即使用密鑰對加密的數(shù)據(jù)進行解密的過程;

加密方式

加密的方式,一般是兩種:對稱加密和非對稱加密;

對稱加密只有一個秘鑰,加密和解密都是用同一個秘鑰,如AES、DES等;

非對稱加密有兩個秘鑰,一個是公鑰,一個是私鑰。使用公鑰對數(shù)據(jù)進行加密,加密后的數(shù)據(jù)只有私鑰可以解密,一般公鑰是公開的,私鑰是不公開的;如RSA、DSA等;

實現(xiàn)原理

Springboot項目中,客戶端通過接口向服務端讀取或寫入敏感數(shù)據(jù)時,常會有這樣的業(yè)務需求:

1、在客戶端向服務器端發(fā)起寫入請求,服務端需要對寫入的敏感數(shù)據(jù)進行加密后存儲;

2、在客戶端從服務器端向外讀取數(shù)據(jù)的時候,需要對輸出的敏感數(shù)據(jù)進行解密;

顯然這種場景,對于加密的方式的選擇,對稱加密是最好的選擇;那么如何實現(xiàn)對寫入請求、讀取請求的敏感數(shù)據(jù)的加密、解密處理呢?解決方案如下:

1、自定義兩個切面注解,分別是加密切面注解、解密切面注解,作用于需要加密或解密的敏感數(shù)據(jù)處理的業(yè)務處理類的具體業(yè)務處理方法上;

2、自定義兩個敏感字段處理注解,分別是加密字段注解、解密字段注解,作用于需要輸入或輸出的對象的敏感字段上;如果輸入對象上標記了加密字段注解,則表示該字段在對內寫入數(shù)據(jù)庫的時候,需要加密處理;同理,如果輸出對象上標記了解密字段注解,則表示該字段在對外輸出的時候,需要進行解密;

3、使用面向切面編程,定義兩個切面類,分別是加密切面類和解密切面類,選擇Spring AOP的環(huán)繞通知來具體實現(xiàn);加密切面類中,以注解的方式定義切入點,用到的注解就是自定義的加密切面注解;

4、如果新增、編輯等寫入類的業(yè)務請求處理方法上標記了加密切面注解,那么寫入請求在正式被業(yè)務處理方法處理前,會命中加密切面類,加密切面類的環(huán)繞通知方法被觸發(fā),然后根據(jù)輸入的參數(shù)對象中的字段是否標記了自定義的加密字段注解,來決定是否對當前字段進行加密處理;

5、同理,如果是查詢等讀取類的業(yè)務請求處理方法上標記了解密切面注解,那么讀取請求被業(yè)務處理類處理完之后,會命中解密切面類,解密切面類的環(huán)繞通知方法被觸發(fā),然后根據(jù)返回對象的字段是否標記了解密字段注解,來決定是否對當前字段進行解密處理。

圖片

實現(xiàn)方案

環(huán)境配置

jdk版本:1.8開發(fā)工具:Intellij iDEA 2020.1

springboot:2.3.9.RELEASE

mybatis-spring-boot-starter:2.1.4

依賴配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.3</version>
</dependency>

示例時序圖

圖片圖片

示例代碼

1、自定義四個注解:@DecryptField(解密字段注解)、@EncryptField(加密字段注解)、@NeedEncrypt(解密切面注解)、@NeedEncrypt(加密切面注解),其中@DecryptField作用于需要解密的字段上;@EncryptField作用于需要加密的字段上;@NeedEncrypt作用于需要對入參數(shù)進行加密處理的方法上;@NeedDecrypt作用于需要對返回值進行解密處理的方法上;

//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
}
//加密字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
//作用于對返回值進行解密處理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
//作用于需要對入參數(shù)進行加密處理的方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

2、把自定義的加密字段注解、解密字段注解標記在需要加密或者解密的字段上;這里表示在寫入人員的手機號碼、身份證號碼、家庭住址門牌號碼時,要進行加密處理;在讀取人員的手機號碼、身份證號碼、家庭住址門牌號碼時,要進行解密處理;

@Slf4j
@Data
public class Person  {
 private Integer id;
 private String userName;
 private String loginNo;
 @EncryptField
 @DecryptField
 private String phoneNumber;
 private String sex;
 @DecryptField
 @EncryptField
 private String IDCard;
 private String address;
 @EncryptField
 @DecryptField
 private String houseNumber;
}

3、把@NeedEncrypt和@NeedDecrypt標記在需要對入參數(shù)、返回值中的敏感字段進行加密、解密處理的業(yè)務處理方法上;

@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
    @Autowired
    private IPersonService personService;
    //添加人員信息
    @PostMapping("/add")
    @NeedEncrypt
    public Person add(@RequestBody Person person, Model model) {
        Person result = this.personService.registe(person);
        log.info("http://增加person執(zhí)行完成");
        return result;
    }
    //人員信息列表查詢
    @GetMapping("/list")
    @NeedDecrypt
    public List<Person> getPerson() {
        List<Person> persons = this.personService.getPersonList();
        log.info("http://查詢person列表執(zhí)行完成");
        return persons;
    }
    //人員信息詳情查詢
    @GetMapping("/{id}")
    @NeedDecrypt
    public Person get(@PathVariable Integer id) {
        Person persnotallow= this.personService.get(id);
        log.info("http://查詢person詳情執(zhí)行完成");
        return person;
    }
}

4、自定義加密切面類(EncryptAop)和解密切面類(DecryptAop):用@NeedEncrypt注解定義加密切點,在加密切點的環(huán)繞通知方法里執(zhí)行到具體的業(yè)務處理方法之前,判斷輸入對象的參數(shù)字段是否標記了@EncryptField(加密字段注解),如果判斷結果為true,則使用java反射對該字段進行加密處理,注意這里引用了hutool的工具包,使用了工具包里的加密和解密方法,這里也可以替換成其他的方式;用@NeedDecrypt注解定義解密切點,在解密切點的環(huán)繞通知方法里執(zhí)行完具體的業(yè)務處理方法之后,判斷輸出對象的參數(shù)字段是否標記了@DecryptField(解密字段注解),如果判斷結果為true,則使用java反射對該 字段進行解密處理;

@Component
@Aspect
@Slf4j
public class EncryptAop {
    /**
     * 定義加密切入點
     */
    @Pointcut(value = "@annotation(com.fanfu.anno.NeedEncrypt)")
    public void pointcut() {
    }


    /**
     * 命中加密切入點的環(huán)繞通知
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("http://環(huán)繞通知 start");
        //獲取命中目標方法的入參數(shù)
        Object[] args = proceedingJoinPoint.getArgs();
        if (args.length > 0) {
            for (Object arg : args) {
                //按參數(shù)的類型進行判斷,如果業(yè)務中還有其他的類型,可酌情增加
                if (arg != null) {
                    if (arg instanceof List) {
                        for (Object tmp : ((List) arg)) {
                            //加密處理
                            this.deepProcess(tmp);
                        }
                    } else {
                        this.deepProcess(arg);
                    }
                }
            }
        }
        //對敏感數(shù)據(jù)加密后執(zhí)行目標方法
        Object result = proceedingJoinPoint.proceed();
        log.info("http://環(huán)繞通知 end");
        return result;
    }


    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //獲取對象的所有字段屬性并遍歷
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判斷字段屬性上是否標記了@EncryptField注解
                if (declaredField.isAnnotationPresent(EncryptField.class)) {
                    //如果判斷結果為真,則取出字段屬性值,進行加密、重新賦值
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //開始敏感字段屬性值加密
                        String decrypt = this.encrypt(value);
                        //把加密后的字段屬性值重新賦值
                        declaredField.set(obj, decrypt);
                    }
                }
            }
        }
    }


    private String encrypt(String value) {
        //這里特別注意一下,對稱加密是根據(jù)密鑰進行加密和解密的,加密和解密的密鑰是相同的,一旦泄漏,就無秘密可言,
        //“fanfu-csdn”就是我自定義的密鑰,這里僅作演示使用,實際業(yè)務中,這個密鑰要以安全的方式存儲;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
        String encryptValue = aes.encryptBase64(value);
        return encryptValue;
    }


}
@Component
@Aspect
@Slf4j
public class DecryptAop {
    /**
     * 定義需要解密的切入點
     */
    @Pointcut(value = "@annotation(com.fanfu.anno.NeedDecrypt)")
    public void pointcut() {
    }


    /**
     * 命中的切入點時的環(huán)繞通知
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("http://環(huán)繞通知 start");
        //執(zhí)行目標方法
        Object result = proceedingJoinPoint.proceed();
        //判斷目標方法的返回值類型
        if (result instanceof List) {
            for (Object tmp : ((List) result)) {
                //數(shù)據(jù)脫敏處理邏輯
                this.deepProcess(tmp);
            }
        } else {
            this.deepProcess(result);
        }
        log.info("http://環(huán)繞通知 end");
        return result;
    }


    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //取出輸出對象的所有字段屬性,并遍歷
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判斷字段屬性上是否標記DecryptField注解
                if (declaredField.isAnnotationPresent(DecryptField.class)) {
                    //如果判斷結果為真,則取出字段屬性數(shù)據(jù)進行解密處理
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //加密數(shù)據(jù)的解密處理
                        value = this.decrypt(value);
                        DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
                        boolean open = annotation.open();
                        //把解密后的數(shù)據(jù)重新賦值
                        declaredField.set(obj, value);
                    }
                }
            }
        }
    }


    private String decrypt(String value) {
        //這里特別注意一下,對稱加密是根據(jù)密鑰進行加密和解密的,加密和解密的密鑰是相同的,一旦泄漏,就無秘密可言,
        //“fanfu-csdn”就是我自定義的密鑰,這里僅作演示使用,實際業(yè)務中,這個密鑰要以安全的方式存儲;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
        String decryptStr = aes.decryptStr(value);
        return decryptStr;
    }
}

加密結果

圖片圖片

解密結果

圖片圖片

總結

這篇著重和大家分享的內容如下:

1、敏感數(shù)據(jù)的一些基礎概念;

2、敏感數(shù)據(jù)處理的解決思路;

3、敏感數(shù)據(jù)處理的具體實現(xiàn)方式;

責任編輯:武曉燕 來源: 凡夫販夫
點贊
收藏

51CTO技術棧公眾號