<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          新書出版——松哥的!

          共 14045字,需瀏覽 29分鐘

           ·

          2021-03-24 17:44

          好朋友松哥出版了一本新書,作為老鐵,在公眾號必須宣傳一把。簡單寫了一點宣傳文案,大家可以看一看。迫不及待的話,可以直接拉到文末領(lǐng)取贈書。

          2011年12月21日,有人在網(wǎng)絡(luò)上公開了一個包含600萬個CSDN用戶資料的數(shù)據(jù)庫,數(shù)據(jù)全部為明文儲存,包含用戶名、密碼以及注冊郵箱。事件發(fā)生后CSDN在微博、官方網(wǎng)站等渠道發(fā)出了聲明,解釋說此數(shù)據(jù)庫系2009年備份所用,因不明原因泄漏,已經(jīng)向警方報案,后又在官網(wǎng)發(fā)出了公開道歉信。在接下來的十多天里,金山、網(wǎng)易、京東、當當、新浪等多家公司被卷入到這次事件中。整個事件中最觸目驚心的莫過于CSDN把用戶密碼明文存儲,由于很多用戶是多個網(wǎng)站共用一個密碼,因此一個網(wǎng)站密碼泄漏就會造成很大的安全隱患。由于有了這么多前車之鑒,我們現(xiàn)在做系統(tǒng)時,密碼都要加密處理。

          1.密碼加密方案進化史

          最早我們使用類似SHA-256這樣的單向Hash算法。用戶注冊成功后,保存在數(shù)據(jù)庫中的不再是用戶的明文密碼,而是經(jīng)過SHA-256加密計算的一個字符串,當用戶進行登錄時,將用戶輸入的明文密碼用SHA-256進行加密,加密完成之后,再和存儲在數(shù)據(jù)庫中的密碼進行比對,進而確定用戶登錄信息是否有效。如果系統(tǒng)遭遇攻擊,最多也只是存儲在數(shù)據(jù)庫中的密文被泄漏。

          這樣就絕對安全了嗎?當然不是的。彩虹表是一個用于加密Hash函數(shù)逆運算的表,通常用于破解加密過的Hash字符串。為了降低彩虹表對系統(tǒng)安全性的影響,人們又發(fā)明了密碼加“鹽”,之前是直接將密碼作為明文進行加密,現(xiàn)在再添加一個隨機數(shù)(即鹽)和密碼明文混合在一起進行加密,這樣即使密碼明文相同,生成的加密字符串也是不同的。當然,這個隨機數(shù)也需要以明文形式和密碼一起存儲在數(shù)據(jù)庫中。當用戶需要登錄時,拿到用戶輸入的明文密碼和存儲在數(shù)據(jù)庫中的鹽一起進行Hash運算,再將運算結(jié)果和存儲在數(shù)據(jù)庫中的密文進行比較,進而確定用戶的登錄信息是否有效。

          密碼加鹽之后,彩虹表的作用就大打折扣了,因為唯一的鹽和明文密碼總會生成唯一的Hash字符。

          然而,隨著計算機硬件的發(fā)展,每秒執(zhí)行數(shù)十億次Hash計算已經(jīng)變得輕輕松松,這意味著即使給密碼加密加鹽也不再安全。

          在Spring Security中,我們現(xiàn)在是用一種自適應(yīng)單向函數(shù)(Adaptive One-way Functions)來處理密碼問題,這種自適應(yīng)單向函數(shù)在進行密碼匹配時,會有意占用大量系統(tǒng)資源(例如CPU、內(nèi)存等),這樣可以增加惡意用戶攻擊系統(tǒng)的難度。在Spring Security中,開發(fā)者可以通過bcrypt、PBKDF2、scrypt以及argon2來體驗這種自適應(yīng)單向函數(shù)加密。

          由于自適應(yīng)單向函數(shù)有意占用大量系統(tǒng)資源,因此每個登錄認證請求都會大大降低應(yīng)用程序的性能,但是Spring Security不會采取任何措施來提高密碼驗證速度,因為它正是通過這種方式來增強系統(tǒng)的安全性。當然,開發(fā)者也可以將用戶名/密碼這種長期憑證兌換為短期憑證,如會話、OAuth2令牌等,這樣既可以快速驗證用戶憑證信息,又不會損失系統(tǒng)的安全性。

          2.PasswordEncoder詳解

          Spring Security中通過PasswordEncoder接口定義了密碼加密和比對的相關(guān)操作:

          public interface PasswordEncoder {
              String encode(CharSequence rawPassword);
              boolean matches(CharSequence rawPassword, String encodedPassword);
              default boolean upgradeEncoding(String encodedPassword) {
                  return false;
              }
          }

          可以看到,PasswordEncoder接口中一共有三個方法:

          1. encode:該方法用來對明文密碼進行加密。
          2. matches:該方法用來進行密碼比對。
          3. upgradeEncoding:該方法用來判斷當前密碼是否需要升級,默認返回false表示不需要升級。

          針對密碼的所有操作,PasswordEncoder接口中都定義好了,不同的實現(xiàn)類將采用不同的密碼加密方案對密碼進行處理。

          2.1 PasswordEncoder常見實現(xiàn)類

          BCryptPasswordEncoder

          BCryptPasswordEncoder使用bcrypt算法對密碼進行加密,為了提高密碼的安全性,bcrypt算法故意降低運行速度,以增強密碼破解的難度。同時BCryptPasswordEncoder “為自己帶鹽”,開發(fā)者不需要額外維護一個“鹽”字段,使用BCryptPasswordEncoder加密后的字符串就已經(jīng)“帶鹽”了,即使相同的明文每次生成的加密字符串都不相同。

          BCryptPasswordEncoder的默認強度為10,開發(fā)者可以根據(jù)自己的服務(wù)器性能進行調(diào)整,以確保密碼驗證時間約為1秒鐘(官方建議密碼驗證時間為1秒鐘,這樣既可以提高系統(tǒng)安全性,又不會過多影響系統(tǒng)運行性能)。

          Argon2PasswordEncoder

          Argon2PasswordEncoder使用Argon2算法對密碼進行加密,Argon2曾在Password Hashing Competition競賽中獲勝。為了解決在定制硬件上密碼容易被破解的問題,Argon2也是故意降低運算速度,同時需要大量內(nèi)存,以確保系統(tǒng)的安全性。

          Pbkdf2PasswordEncoder

          Pbkdf2PasswordEncoder使用PBKDF2算法對密碼進行加密,和前面幾種類似,PBKDF2算法也是一種故意降低運算速度的算法,當需要FIPS(Federal Information Processing Standard,美國聯(lián)邦信息處理標準)認證時,PBKDF2算法是一個很好的選擇。

          SCryptPasswordEncoder

          SCryptPasswordEncoder使用scrypt算法對密碼進行加密,和前面的幾種類似,scrypt也是一種故意降低運算速度的算法,而且需要大量內(nèi)存。

          這四種就是我們前面所說的自適應(yīng)單向函數(shù)加密。除了這幾種,還有一些基于消息摘要算法的加密方案,這些方案都已經(jīng)不再安全,但是出于兼容性考慮,Spring Security并未移除相關(guān)類,主要有LdapShaPasswordEncoder、MessageDigestPasswordEncoder、Md4Password Encoder、StandardPasswordEncoder以及NoOpPasswordEncoder(密碼明文存儲),這五種皆已廢棄,這里對這些類也不做過多介紹。

          除了上面介紹的這幾種之外,還有一個非常重要的密碼加密工具類,那就是DelegatingPasswordEncoder。

          2.2 DelegatingPasswordEncoder

          根據(jù)前文的介紹,讀者可能會認為Spring Security中默認的密碼加密方案應(yīng)該是四種自適應(yīng)單向加密函數(shù)中的一種,其實不然,在Spring Security 5.0之后,默認的密碼加密方案其實是DelegatingPasswordEncoder。

          從名字上來看,DelegatingPasswordEncoder是一個代理類,而并非一種全新的密碼加密方案。

          DelegatingPasswordEncoder主要用來代理上面介紹的不同的密碼加密方案。為什么采用DelegatingPasswordEncoder而不是某一個具體加密方式作為默認的密碼加密方案呢?主要考慮了如下三方面的因素:

          1. 兼容性:使用DelegatingPasswordEncoder可以幫助許多使用舊密碼加密方式的系統(tǒng)順利遷移到Spring Security中,它允許在同一個系統(tǒng)中同時存在多種不同的密碼加密方案。
          2. 便捷性:密碼存儲的最佳方案不可能一直不變,如果使用DelegatingPasswordEncoder作為默認的密碼加密方案,當需要修改加密方案時,只需要修改很小一部分代碼就可以實現(xiàn)。
          3. 穩(wěn)定性:作為一個框架,Spring Security不能經(jīng)常進行重大更改,而使用Delegating PasswordEncoder可以方便地對密碼進行升級(自動從一個加密方案升級到另外一個加密方案)。

          那么DelegatingPasswordEncoder到底是如何代理其他密碼加密方案的?又是如何對加密方案進行升級的?我們就從PasswordEncoderFactories類開始看起,因為正是由它里邊的靜態(tài)方法createDelegatingPasswordEncoder提供了默認的DelegatingPasswordEncoder實例:

          public class PasswordEncoderFactories {
              public static PasswordEncoder createDelegatingPasswordEncoder() {
                  String encodingId = "bcrypt";
                  Map<String, PasswordEncoder> encoders = new HashMap<>();
                  encoders.put(encodingId, new BCryptPasswordEncoder());
                  encoders.put("ldap"new org.springframework.security.crypto
                                                     .password.LdapShaPasswordEncoder());
                  encoders.put("MD4"new org.springframework.security.crypto
                                                          .password.Md4PasswordEncoder());
                  encoders.put("MD5"new org.springframework.security.crypto
                                        .password.MessageDigestPasswordEncoder("MD5"));
                  encoders.put("noop", org.springframework.security.crypto.password
                                                     .NoOpPasswordEncoder.getInstance());
                  encoders.put("pbkdf2"new Pbkdf2PasswordEncoder());
                  encoders.put("scrypt"new SCryptPasswordEncoder());
                  encoders.put("SHA-1"new org.springframework.security.crypto
                                      .password.MessageDigestPasswordEncoder("SHA-1"));
                  encoders.put("SHA-256"new org.springframework.security.crypto
                                   .password.MessageDigestPasswordEncoder("SHA-256"));
                  encoders.put("sha256"new org.springframework.security.crypto
                                                    .password.StandardPasswordEncoder());
                  encoders.put("argon2"new Argon2PasswordEncoder());
                  return new DelegatingPasswordEncoder(encodingId, encoders);
              }
              private PasswordEncoderFactories() {}
          }

          可以看到,在createDelegatingPasswordEncoder方法中,首先定義了encoders變量,encoders中存儲了每一種密碼加密方案的id和所對應(yīng)的加密類,例如bcrypt對應(yīng)著BcryptPassword Encoder、argon2對應(yīng)著Argon2PasswordEncoder、noop對應(yīng)著NoOpPasswordEncoder。

          encoders創(chuàng)建完成后,最終新建一個DelegatingPasswordEncoder實例,并傳入encodingId和encoders變量,其中encodingId默認值為bcrypt,相當于代理類中默認使用的加密方案是BCryptPasswordEncoder。

          我們來分析一下DelegatingPasswordEncoder類的源碼,由于源碼比較長,我們就先從它的屬性開始看起:

          public class DelegatingPasswordEncoder implements PasswordEncoder {
              private static final String PREFIX = "{";
              private static final String SUFFIX = "}";
              private final String idForEncode;
              private final PasswordEncoder passwordEncoderForEncode;
              private final Map<String, PasswordEncoder> idToPasswordEncoder;
              private PasswordEncoder defaultPasswordEncoderForMatches = 
                                                          new UnmappedIdPasswordEncoder();
          }
          1. 首先定義了前綴PREFIX和后綴SUFFIX,用來包裹將來生成的加密方案的id。
          2. idForEncode表示默認的加密方案id。
          3. passwordEncoderForEncode表示默認的加密方案(BCryptPasswordEncoder),它的值是根據(jù)idForEncode從idToPasswordEncoder集合中提取出來的。
          4. idToPasswordEncoder用來保存id和加密方案之間的映射。
          5. defaultPasswordEncoderForMatches是指默認的密碼比對器,當根據(jù)密碼加密方案的id無法找到對應(yīng)的加密方案時,就會使用默認的密碼比對器。defaultPasswordEncoderForMatches的默認類型是UnmappedIdPasswordEncoder,在UnmappedIdPasswordEncoder的matches方法中并不會做任何密碼比對操作,直接拋出異常。
          6. 最后看到的DelegatingPasswordEncoder也是PasswordEncoder接口的子類,所以接下來我們就來重點分析PasswordEncoder接口中三個方法在DelegatingPasswordEncoder中的具體實現(xiàn)。首先來看encode方法:
          @Override
          public String encode(CharSequence rawPassword) {
              return PREFIX + this.idForEncode + SUFFIX 
                                   + this.passwordEncoderForEncode.encode(rawPassword);
          }

          encode方法的實現(xiàn)邏輯很簡單,具體的加密工作還是由加密類來完成,只不過在密碼加密完成后,給加密后的字符串加上一個前綴{id},用來描述所采用的具體加密方案。因此,encode方法加密出來的字符串格式類似如下形式:

          {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
          {noop}123
          {pbkdf2}23b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4

          不同的前綴代表了后面的字符串采用了不同的加密方案。

          再來看密碼比對方法matches:

          @Override
          public boolean matches(CharSequence rawPassword, 
                                      String prefixEncodedPassword)
           
          {
              if (rawPassword == null && prefixEncodedPassword == null) {
                  return true;
              }
              String id = extractId(prefixEncodedPassword);
              PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
              if (delegate == null) {
                  return this.defaultPasswordEncoderForMatches
                      .matches(rawPassword, prefixEncodedPassword);
              }
              String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
              return delegate.matches(rawPassword, encodedPassword);
          }
          private String extractId(String prefixEncodedPassword) {
              if (prefixEncodedPassword == null) {
                  return null;
              }
              int start = prefixEncodedPassword.indexOf(PREFIX);
              if (start != 0) {
                  return null;
              }
              int end = prefixEncodedPassword.indexOf(SUFFIX, start);
              if (end < 0) {
                  return null;
              }
              return prefixEncodedPassword.substring(start + 1, end);
          }

          在matches方法中,首先調(diào)用extractId方法從加密字符串中提取出具體的加密方案id,也就是{}中的字符,具體的提取方式就是字符串截取。拿到id之后,再去idToPasswordEncoder集合中獲取對應(yīng)的加密方案,如果獲取到的為null,說明不存在對應(yīng)的加密實例,那么就會采用默認的密碼匹配器defaultPasswordEncoderForMatches;如果根據(jù)id獲取到了對應(yīng)的加密實例,則調(diào)用其matches方法完成密碼校驗。

          可以看到,這里的matches方法非常靈活,可以根據(jù)加密字符串的前綴,去查找到不同的加密方案,進而完成密碼校驗。同一個系統(tǒng)中,加密字符串可以使用不同的前綴而互不影響。

          最后,我們再來看一下DelegatingPasswordEncoder中的密碼升級方法upgradeEncoding:

          @Override
          public boolean upgradeEncoding(String prefixEncodedPassword) {
              String id = extractId(prefixEncodedPassword);
              if (!this.idForEncode.equalsIgnoreCase(id)) {
                  return true;
              }
              else {
                  String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
                  return this.idToPasswordEncoder.get(id)
                                                        .upgradeEncoding(encodedPassword);
              }
          }

          可以看到,如果當前加密字符串所采用的加密方案不是默認的加密方案(BcryptPassword Encoder),就會自動進行密碼升級,否則就調(diào)用默認加密方案的upgradeEncoding方法判斷密碼是否需要升級。至此,我們將Spring Security中的整個加密體系向讀者簡單介紹了一遍,接下來我們通過幾個實際的案例來看一下加密方案要怎么用。


          以上內(nèi)容節(jié)選自《深入淺出 Spring Security》一書,作者江南一點雨,也就是松哥,我的好朋友,鐵哥們,他是華為云MVP,華為云云享專家,之前出版過另外一本書《Spring Boot+Vue全棧開發(fā)實戰(zhàn)》,對 Spring 全家桶有深入研究。想要直接購買的,可以點擊下面的鏈接。

          松哥在新書出版的第一時間就給我送了一本簽名版。好像字寫得和我一個水平,都不咋滴哈。

          我給公號的讀者申請了五本,后臺回復關(guān)鍵字「松哥」就行了,按照順序,第 1、50、100、150、200 各送一本,完全憑運氣了哈,我會在下次發(fā)文的時候人工數(shù)一數(shù)的,到時候會告訴中獎的小伙伴,記得給我發(fā)郵寄信息喲~

          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本色情视频网站 | 又污又黄的网站 | 亚洲视频在线观看不卡 | 日韩人妻久久亚洲 | 房色情五月天 |