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