Java面試題沖刺第二十六天--實(shí)戰(zhàn)編程
面試題1:你們是怎樣保存用戶(hù)密碼等敏感數(shù)據(jù)的?
本題回答參考朱曄的《Java業(yè)務(wù)開(kāi)發(fā)常見(jiàn)錯(cuò)誤100例》
我們知道,用戶(hù)名、密碼、身份證等都屬于用戶(hù)敏感信息,其中最敏感的數(shù)據(jù)恐怕就是用戶(hù)的密碼了。黑客一旦竊取了用戶(hù)密碼,就可以登錄進(jìn)用戶(hù)的賬號(hào),消耗其資產(chǎn)、發(fā)布不良信息等;更可怕的是,有些用戶(hù)至始至終都是使用一套密碼,密碼一旦泄露,就可以被黑客通過(guò)撞庫(kù)來(lái)登錄全網(wǎng)各大平臺(tái),嘿嘿嘿。
為了防止密碼泄露,最重要的原則是不要在數(shù)據(jù)庫(kù)保存用戶(hù)原始密碼。
大家經(jīng)常說(shuō),不要明文保存用戶(hù)密碼,應(yīng)該把密碼通過(guò) MD5 加密后保存。這的確是一個(gè)正確的方向,但這個(gè)說(shuō)法并不準(zhǔn)確。
首先,MD5 其實(shí)不是真正的加密算法。所謂加密算法,是可以使用密鑰把明文加密為密文,隨后還可以使用密鑰解密出明文,是雙向的。而 MD5 是散列、哈希算法或者摘要算法。不管多長(zhǎng)的數(shù)據(jù),使用 MD5 運(yùn)算后得到的都是固定長(zhǎng)度的摘要信息或指紋信息,無(wú)法再解密為原始數(shù)據(jù)。所以,MD5 是單向的。最重要的是,僅僅使用 MD5 對(duì)密碼進(jìn)行摘要,并不安全。
比如,使用如下代碼在保持用戶(hù)信息時(shí),對(duì)密碼進(jìn)行了 MD5 計(jì)算:
UserData userData = new UserData(); userData.setId(1L); userData.setName(name); //密碼字段使用MD5哈希后保存 userData.setPassword(DigestUtils.md5Hex(password)); return userRepository.save(userData);
通過(guò)輸出,可以看到密碼是 32 位的 MD5:
"password": "325a2cc052914ceeb8c19016c091d2ac"
然后,再去破解網(wǎng)站試一下這個(gè) MD5,就可以得到原始密碼是 salt,也就知道了鹽值是 salt:
其實(shí),知道鹽是什么沒(méi)什么關(guān)系,關(guān)鍵的是我們是在代碼里寫(xiě)死了鹽,并且鹽很短、所有用戶(hù)都是這個(gè)鹽。這么做就會(huì)有三個(gè)問(wèn)題:
- 因?yàn)辂}太短、太簡(jiǎn)單了,如果用戶(hù)原始密碼也很簡(jiǎn)單,那么整個(gè)拼起來(lái)的密碼也很短,這樣一般的 MD5 破解網(wǎng)站都可以直接解密這個(gè) MD5,除去鹽就是原始密碼了。
- 相同的鹽,意味著使用相同密碼的用戶(hù) MD5 值是一樣的,知道了一個(gè)用戶(hù)的密碼就可能知道了多個(gè)。
- 黑客也可以使用這個(gè)鹽來(lái)構(gòu)建一張彩虹表,也就是字典表,雖然會(huì)花不少代價(jià),但是一旦構(gòu)建完成,所有人的密碼都可以被破解。
所以,最好是每一個(gè)密碼都有獨(dú)立的鹽,并且鹽要長(zhǎng)一點(diǎn),比如超過(guò) 20 位。
第二,雖然說(shuō)每個(gè)人的鹽最好不同,但也不建議將一部分用戶(hù)數(shù)據(jù)作為鹽。比如,使用用戶(hù)名作為鹽:
userData.setPassword(DigestUtils.md5Hex(name + password));
如果世界上所有的系統(tǒng)都是按照這個(gè)方案來(lái)保存密碼,那么 root、admin 這樣的用戶(hù)使用再?gòu)?fù)雜的密碼也總有一天會(huì)被破解,因?yàn)楹诳蛡兺耆梢葬槍?duì)這些常用用戶(hù)名來(lái)做彩虹表。所以,鹽最好是隨機(jī)的值,并且是全球唯一的,意味著全球不可能有現(xiàn)成的彩虹表給你用。
正確的做法是,使用全球唯一的、和用戶(hù)無(wú)關(guān)的、足夠長(zhǎng)的隨機(jī)值作為鹽。比如,可以使用 UUID 作為鹽,把鹽一起保存到數(shù)據(jù)庫(kù)中:
userData.setSalt(UUID.randomUUID().toString());
userData.setPassword(DigestUtils.md5Hex(userData.getSalt() + password));
并且每次用戶(hù)修改密碼的時(shí)候都重新計(jì)算鹽,重新保存新的密碼。
需要注意的是,這么做雖然黑客已經(jīng)很難通過(guò)彩虹表來(lái)破解密碼了,但是仍然有可能暴力破解密碼,也就是對(duì)于同一個(gè)用戶(hù)名使用常見(jiàn)的密碼逐一嘗試登錄。因此,除了做好密碼哈希保存的工作外,我們還要建設(shè)一套完善的安全防御機(jī)制,在感知到暴力破解危害的時(shí)候,開(kāi)啟短信驗(yàn)證、圖形驗(yàn)證碼、賬號(hào)暫時(shí)鎖定等防御機(jī)制來(lái)抵御暴力破解。
那么姓名和身份證又是怎么保存的?
我們把姓名和身份證,叫做二要素。
現(xiàn)在互聯(lián)網(wǎng)非常發(fā)達(dá),很多服務(wù)都可以在網(wǎng)上辦理,很多網(wǎng)站僅僅依靠二要素來(lái)確認(rèn)你是誰(shuí)。所以,二要素是比較敏感的數(shù)據(jù),如果在數(shù)據(jù)庫(kù)中明文保存,那么數(shù)據(jù)庫(kù)被攻破后,黑客就可能拿到大量的二要素信息。如果這些二要素被用來(lái)申請(qǐng)貸款等,后果不堪設(shè)想。
之前我們提到的單向散列算法(MD5),顯然不適合用來(lái)加密保存二要素,因?yàn)閿?shù)據(jù)無(wú)法解密。這個(gè)時(shí)候,我們需要選擇真正的加密算法??晒┻x擇的算法,包括對(duì)稱(chēng)加密和非對(duì)稱(chēng)加密算法兩類(lèi)。
- 對(duì)稱(chēng)加密算法:是使用相同的密鑰進(jìn)行加密和解密。使用對(duì)稱(chēng)加密算法來(lái)加密雙方的通信的話(huà),雙方需要先約定一個(gè)密鑰,加密方才能加密,接收方才能解密。如果密鑰在發(fā)送的時(shí)候被竊取,那么加密就是白忙一場(chǎng)。因此,這種加密方式的特點(diǎn)是,加密速度比較快,但是密鑰傳輸分發(fā)有泄露風(fēng)險(xiǎn)。
- 非對(duì)稱(chēng)加密算法:或者叫公鑰密碼算法。公鑰密碼是由一對(duì)密鑰對(duì)構(gòu)成的,使用公鑰或者說(shuō)加密密鑰來(lái)加密,使用私鑰或者說(shuō)解密密鑰來(lái)解密,公鑰可以任意公開(kāi),私鑰不能公開(kāi)。使用非對(duì)稱(chēng)加密的話(huà),通信雙方可以?xún)H分享公鑰用于加密,加密后的數(shù)據(jù)沒(méi)有私鑰無(wú)法解密。因此,這種加密方式的特點(diǎn)是,加密速度比較慢,但是解決了密鑰的配送分發(fā)安全問(wèn)題。
但是,對(duì)于保存敏感信息的場(chǎng)景來(lái)說(shuō),加密和解密都是我們的服務(wù)端程序,不太需要考慮密鑰的分發(fā)安全性,也就是說(shuō)使用非對(duì)稱(chēng)加密算法沒(méi)有太大的意義。我們一般會(huì)使用對(duì)稱(chēng)加密算法來(lái)加密數(shù)據(jù)。常見(jiàn)的對(duì)稱(chēng)加密算法有:DES、3DES 和 AES。
面試題2:怎么控制用戶(hù)請(qǐng)求的冪等性的?
冪等性:對(duì)于同一筆業(yè)務(wù)操作,不管調(diào)用多少次,得到的結(jié)果都是一樣的。
當(dāng)然,有些操作是天然冪等的,如:
- 查詢(xún)操作:查詢(xún)一次和查詢(xún)多次,在數(shù)據(jù)不變的情況下,查詢(xún)結(jié)果是一樣的。select是天然的冪等操作;
- 刪除操作:刪除操作也是冪等的,刪除一次和多次刪除都是把數(shù)據(jù)刪除。
后臺(tái)控制冪等性的幾種途徑:
1.設(shè)置唯一索引:防止新增臟數(shù)據(jù)
比如支付寶的資金賬戶(hù),支付寶也有用戶(hù)賬戶(hù),每個(gè)用戶(hù)只能有一個(gè)資金賬戶(hù),怎么防止給用戶(hù)創(chuàng)建資金賬戶(hù)多個(gè),那么給資金賬戶(hù)表中的用戶(hù)ID加唯一索引,所以一個(gè)用戶(hù)新增成功一個(gè)資金賬戶(hù)記錄。要點(diǎn):唯一索引或唯一組合索引來(lái)防止新增數(shù)據(jù)存在臟數(shù)據(jù)(當(dāng)表存在唯一索引,并發(fā)時(shí)新增報(bào)錯(cuò)時(shí),再查詢(xún)一次就可以了,數(shù)據(jù)應(yīng)該已經(jīng)存在了,返回結(jié)果即可);
2.token機(jī)制:防止頁(yè)面重復(fù)提交
原理上通過(guò)session token來(lái)實(shí)現(xiàn)的(也可以通過(guò)redis來(lái)實(shí)現(xiàn))。當(dāng)客戶(hù)端請(qǐng)求頁(yè)面時(shí),服務(wù)器會(huì)生成一個(gè)隨機(jī)數(shù)Token,并且將Token放置到session當(dāng)中,然后將Token發(fā)給客戶(hù)端(一般通過(guò)構(gòu)造hidden表單)。下次客戶(hù)端提交請(qǐng)求時(shí),Token會(huì)隨著表單一起提交到服務(wù)器端。
服務(wù)器端第一次驗(yàn)證相同過(guò)后,會(huì)將session中的Token值更新下,若用戶(hù)重復(fù)提交,第二次的驗(yàn)證判斷將失敗,因?yàn)橛脩?hù)提交的表單中的Token沒(méi)變,但服務(wù)器端session中Token已經(jīng)改變了。
3.悲觀(guān)鎖
獲取數(shù)據(jù)的時(shí)候加鎖獲取。
select * from table_xxx where id='xxx' for update;
注意:id字段一定是主鍵或者唯一索引,不然是鎖表,會(huì)死人的;悲觀(guān)鎖使用時(shí)一般伴隨事務(wù)一起使用,數(shù)據(jù)鎖定時(shí)間可能會(huì)很長(zhǎng),根據(jù)實(shí)際情況選用;
4.樂(lè)觀(guān)鎖
樂(lè)觀(guān)鎖只是在更新數(shù)據(jù)那一刻鎖表,其他時(shí)間不鎖表,所以相對(duì)于悲觀(guān)鎖,效率更高。樂(lè)觀(guān)鎖的實(shí)現(xiàn)方式多種多樣,可以通過(guò)version或者其他狀態(tài)條件判斷:
- 通過(guò)版本號(hào)實(shí)現(xiàn)
update table_xxx set name=#name#,version=version+1 where version=#version#;
- 通過(guò)條件限制
update table_xxx set avai_amount=avai_amount where avai_amount >= 0
5.分布式鎖
如果是分布式系統(tǒng),構(gòu)建全局唯一索引比較困難,例如唯一性的字段沒(méi)法確定,這時(shí)候可以引入分布式鎖,通過(guò)第三方的系統(tǒng)(redis或zookeeper),在業(yè)務(wù)系統(tǒng)插入數(shù)據(jù)或者更新數(shù)據(jù),獲取分布式鎖,然后做操作,之后釋放鎖,這樣其實(shí)是把多線(xiàn)程并發(fā)的鎖的思路,引入多多個(gè)系統(tǒng),也就是分布式系統(tǒng)中得解決思路。
要點(diǎn):某個(gè)長(zhǎng)流程處理過(guò)程要求不能并發(fā)執(zhí)行,可以在流程執(zhí)行之前根據(jù)某個(gè)標(biāo)志(用戶(hù)ID+后綴等)獲取分布式鎖,其他流程執(zhí)行時(shí)獲取鎖就會(huì)失敗,也就是同一時(shí)間該流程只能有一個(gè)能執(zhí)行成功,執(zhí)行完成后,釋放分布式鎖(分布式鎖要第三方系統(tǒng)提供);
面試題3:你們是如何預(yù)防SQL注入問(wèn)題的?
SQL注入攻擊的總體思路
- 尋找到SQL注入的位置
- 判斷服務(wù)器類(lèi)型和后臺(tái)數(shù)據(jù)庫(kù)類(lèi)型
- 針對(duì)不通的服務(wù)器和數(shù)據(jù)庫(kù)特點(diǎn)進(jìn)行SQL注入攻擊
預(yù)防方式:
1、PreparedStatement(簡(jiǎn)單有效)
采用預(yù)編譯語(yǔ)句集,它內(nèi)置了處理SQL注入的能力,只要使用它的setXXX方法傳值即可。
sql注入只對(duì)sql語(yǔ)句的準(zhǔn)備(編譯)過(guò)程有破壞作用,而PreparedStatement在執(zhí)行階段只是把輸入串作為數(shù)據(jù)處理,不再對(duì)sql語(yǔ)句進(jìn)行解析
,因此也就避免了sql注入問(wèn)題。
2、使用正則表達(dá)式過(guò)濾傳入的參數(shù)
要引入的包:
import java.util.regex.*;
正則表達(dá)式:
private String CHECKSQL = “^(.+)\\sand\\s(.+)|(.+)\\sor(.+)\\s$”;
判斷是否匹配:
Pattern.matches(CHECKSQL,targerStr);
下面是常用來(lái)過(guò)濾參數(shù)是否存在SQL注入的正則表達(dá)式:
- 檢測(cè)SQL meta-characters的正則表達(dá)式 : /(\%27)|(\')|(\-\-)|(\%23)|(#)/ix
- 修正檢測(cè)SQL meta-characters的正則表達(dá)式 : /((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(:))/i
- 典型的SQL 注入攻擊的正則表達(dá)式 : /\w*((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix
- 檢測(cè)SQL注入,UNION查詢(xún)關(guān)鍵字的正則表達(dá)式 : /((\%27)|(\'))union/ix(\%27)|(\')
3.使用正則表達(dá)式過(guò)濾傳入的URL
比較通用的一個(gè)方法,jsp中調(diào)用該函數(shù)檢查是否包函非法字符,防止SQL從URL注入:
(||之間的參數(shù)可以根據(jù)自己程序的需要添加)
public static boolean sql_inj(String str){ String inj_str = "'|and|exec|insert|select|delete|update| count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,"; String inj_stra[] = split(inj_str,"|"); for (int i=0 ; i < inj_stra.length ; i++ ){ if (str.indexOf(inj_stra[i])>=0){ return true; } } return false; }
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- java synchronized 鎖機(jī)制原理詳解
- Java實(shí)現(xiàn)遞歸計(jì)算n的階乘
- java多線(xiàn)程模擬實(shí)現(xiàn)售票功能
- java實(shí)現(xiàn)多客戶(hù)聊天功能
- Java CharacterEncodingFilter過(guò)濾器的理解和配置案例詳解
- Java面試題沖刺第二十六天--實(shí)戰(zhàn)編程2
- Java面試題沖刺第二十五天--并發(fā)編程3
- Java DatabaseMetaData用法案例詳解
- Java 動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)示例
- Java中的equsals和==
相關(guān)文章
JAVA復(fù)制數(shù)組和重置數(shù)組大小操作
這篇文章主要介紹了JAVA復(fù)制數(shù)組和重置數(shù)組大小操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09使用ResponseEntity處理API返回問(wèn)題
這篇文章主要介紹了使用ResponseEntity處理API返回問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件
這篇文章主要介紹了詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12SpringMVC 接收前端傳遞的參數(shù)四種方式小結(jié)
這篇文章主要介紹了SpringMVC 接收前端傳遞的參數(shù)四種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10親身體驗(yàn)Intellij?Idea從卡頓到順暢全過(guò)程
這篇文章主要介紹了親身體驗(yàn)Intellij?Idea從卡頓到順暢全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Java使用JDBC連接Oracle_MSSQL實(shí)例代碼
這篇文章主要介紹了Java使用JDBC連接Oracle_MSSQL實(shí)例代碼,需要的朋友可以參考下2014-01-01Spring?Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪(fǎng)問(wèn)控制的操作方法
這篇文章主要介紹了Spring?Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪(fǎng)問(wèn)控制,資源權(quán)限表達(dá)式動(dòng)態(tài)權(quán)限控制在Spring Security也是可以實(shí)現(xiàn)的,首先開(kāi)啟方法級(jí)別的注解安全控制,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04