將 Shiro 做爲應用的權限基礎 五:密碼的加密/解密在Spring中的應用

考慮系統密碼的安全,目前大多數系統都不會把密碼以明文的形式存放到數據庫中。java

一把會採起如下幾種方式對密碼進行處理算法

密碼的存儲數據庫

「編碼」存儲apache

Shiro 提供了 base64和 16 進制字符串編碼/解碼的 API支持,方便一些編碼解碼操做。 Shiro內部的一些數據的存儲/表示都使用了 base64和 16 進制字符串。安全

下面兩端代碼分別對其進行演示app

Stringstr = "hello"; 

Stringbase64Encoded = Base64.encodeToString(str.getBytes()); 

Stringstr2 = Base64.decodeToString(base64Encoded); 

Assert.assertEquals(str,str2);

經過如上方式能夠進行 base64編碼/解碼操做dom

Stringstr = "hello"; 

Stringbase64Encoded = Hex.encodeToString(str.getBytes()); 

Stringstr2 =newString(Hex.decode(base64Encoded.getBytes())); 

Assert.assertEquals(str,str2);

經過如上方式能夠進行 16 進制字符串編碼/解碼操做。ide

Hash存儲網站

散列算法通常用於生成數據的摘要信息,是一種不可逆的算法,通常適合存儲密碼之類的數據,常見的散列算法如 MD五、SHA等。通常進行散列時最好提供一個 salt(鹽),好比加密密碼「admin」,產生的散列值是「21232f297a57a5a743894a0e4a801fc3」,能夠到一些 md5解密網站很容易的經過散列值獲得密碼「admin」,即若是直接對密碼進行散列相對來講破解更容易,此時咱們能夠加一些只有系統知道的干擾數據,如用戶名和 ID(即鹽);這樣散列的對象是「密碼+用戶名+ID」,這樣生成的散列值相對來講更難破解。ui

Stringstr = "hello"; 

Stringsalt = "123"; 

Stringmd5 =new Md5Hash(str, salt).toString();//還能夠轉換爲  toBase64()/toHex()

如上代碼經過鹽「123」MD5散列「hello」。另外散列時還能夠指定散列次數,如 2次表示:md5(md5(str)):「new Md5Hash(str, salt, 2).toString()」

Stringstr = "hello"; 

Stringsalt = "123"; 

Stringsha1 =new Sha256Hash(str, salt).toString();

使用 SHA256 算法生成相應的散列數據,另外還有如 SHA一、SHA512算法。

Shiro 還提供了通用的散列支持:

Stringstr = "hello"; 

Stringsalt = "123"; 

//內部使用MessageDigest 

StringsimpleHash =new SimpleHash("SHA-1", str, salt).toString();

經過調用 SimpleHash 時指定散列算法,其內部使用了 Java的 MessageDigest 實現。爲了方便使用,Shiro提供了 HashService,默認提供了 DefaultHashService實現

DefaultHashServicehashService =new DefaultHashService(); //默認算法 SHA-512 

hashService.setHashAlgorithmName("SHA-512");

hashService.setPrivateSalt(newSimpleByteSource("123"));//私鹽,默認無

hashService.setGeneratePublicSalt(true);//是否生成公鹽,默認false 

hashService.setRandomNumberGenerator(new  SecureRandomNumberGenerator());//用於生成公鹽。默認就這個

hashService.setHashIterations(1);//生成 Hash 值的迭代次數

HashRequestrequest =new HashRequest.Builder() 

.setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello"))

.setSalt(ByteSource.Util.bytes("123")).setIterations(2).build();

Stringhex =hashService.computeHash(request).toHex();

加密存儲

Shiro 還提供對稱式加密/解密算法的支持,如 AES、Blowfish等;當前尚未提供對非對稱加密/解密算法支持,將來版本可能提供。

AES 算法實現:

AesCipherServiceaesCipherService =new AesCipherService(); 

aesCipherService.setKeySize(128);//設置 key 長度

//生成 key 

Keykey=aesCipherService.generateNewKey(); 

Stringtext = "hello"; 

//加密

StringencrptText =

aesCipherService.encrypt(text.getBytes(),key.getEncoded()).toHex(); 

//解密

Stringtext2 = 

newString(aesCipherService.decrypt(Hex.decode(encrptText),key.getEncoded()).getBytes()); 

Assert.assertEquals(text, text2);

密碼的驗證

使用「密碼的存儲」章節中其中一種存儲方式存儲密碼以後,接下來就是登錄時密碼的驗證.

該過程主要涉及到兩個問題:

(1)    讓Shiro知道數據庫中存儲的密碼是經過什麼方式存儲的?

能夠在指定比較器時進行設置

(2)    讓Shiro知道如何比對數據庫與登陸時的密碼?

Shiro主要經過CredentialsMatcher的子類來實現密碼的對比,Shiro已經實現了比較常見的Matcher,例如Md5CredentialsMatcher,Sha256CredentialsMatcher等等,若是Shiro提供的Matcher不能知足需求,還能夠自定義Matcher.

Spring集成Shiro密碼驗證明例

本小節以Spring集成Shiro爲基礎,介紹如何使用MD5對密碼進行加密,添加密碼加密功能須要通過如下三步

保存密碼

做爲密碼的數據源頭,首先要確保在密碼保存時就使用MD5加密

在Spring的Controller對應的方法中,使用如下方法保存密碼

@RequestMapping("/setting/user/add")
  public String addUser(@Valid User user, BindingResult result, Map<String, Object> model,
      @RequestParam(value = "action") String action) {
    String cryptedPwd = new Md5Hash(user.getPwd()).toString();
    user.setPwd(cryptedPwd);
    userService.saveUser(user);
    return "redirect:/setting/user/" + user.getName();
  }

配置Matcher

當數據庫中存的是MD5形式的密碼後,就要告訴shiro如何對登錄時輸入的密碼進行對比,在這裏採用默認的HashedCredentialsMatcher做爲比較器

 Shiro-config.xml配置以下

<!—在自定義的Realm中配置Matcher,並指定加密算法-->

         <beanid="customerRealm" class="com. test.security.CustomerRealm">

                   <propertyname="credentialsMatcher">

                            <beanclass="org.apache.shiro.authc.credential.HashedCredentialsMatcher">

                                     <propertyname="hashAlgorithmName" value="MD5" />

                            </bean>

                   </property>

         </bean>

密碼校驗

對於CustomerRealm而言,不須要關心密碼是如何被匹配的,只須要將用戶輸入的密碼傳入AuthenticationInfo對象中便可

@Override

  protectedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)

     throws AuthenticationException {

   // 獲取基於用戶名和密碼的令牌,authcToken是從LoginController裏面//currentUser.login(token)傳過來的

   UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

   User user = userService.findUserByName(token.getUsername());

    if(null != user) {

     AuthenticationInfo authcInfo =

         new SimpleAuthenticationInfo(user.getName(), user.getPwd(),user.getName());

     this.setSession(GCloudConstant.CURRENT_USER,user.getName());

     return authcInfo;

    }else {

     return null;

    }

  }
相關文章
相關標籤/搜索