在涉及到密碼存儲問題上,應該加密/生成密碼摘要存儲,而不是存儲明文密碼。爲何要加密:網絡安全問題是一個很大的隱患,用戶數據泄露事件層出不窮,好比12306帳號泄露。java
Shiro提供了base64和16進制字符串編碼/解碼的API支持,方便一些編碼解碼操做,想了解本身百度API操做用法。mysql
看一張圖,瞭解Shiro提供的加密算法:算法
本文重點講shiro提供的第二種:不可逆加密。spring
散列算法通常用於生成數據的摘要信息,是一種不可逆的算法,通常適合存儲密碼之類的數據,常見的散列算法如MD五、SHA等。通常進行散列時最好提供一salt(鹽),好比加密密碼「admin」,產生的散列值是「21232f297a57a5a743894a0e4a801fc3」,能夠到一些md5解密網站很容易的經過散列值獲得密碼「admin」,即若是直接對密碼進行散列相對來講破解更容易,此時咱們能夠加一些只有系統知道的干擾數據,如用戶名和ID(即鹽);這樣散列的對象是「密碼+用戶名+ID」,這樣生成的散列值相對來講更難破解。sql
常見的算法有:MD5,SHA算法:數據庫
MD5算法是1991年發佈的一項數字簽名加密算法,它當時解決了MD4算法的安全性缺陷,成爲應用很是普遍的一種算法。做爲Hash函數的一個應用實例。apache
SHA誕生於1993年,全稱是安全散列算法(Secure Hash Algorithm),由美國國家安全局(NSA)設計,以後被美國標準與技術研究院(NIST)收錄到美國的聯邦信息處理標準(FIPS)中,成爲美國國家標準,SHA(後來被稱做SHA-0)於1995被SHA-1(RFC3174)替代。SHA-1生成長度爲160bit的摘要信息串,雖然以後又出現了SHA-22四、SHA-25六、SHA-384和SHA-512等被統稱爲「SHA-2」的系列算法,但仍以SHA-1爲主流。安全
數據庫User設計:網絡
-
CREATE TABLE `sys_users` (
-
`id` bigint(20) NOT NULL AUTO_INCREMENT,
-
`username` varchar(100) DEFAULT NULL,
-
`password` varchar(100) DEFAULT NULL,
-
`salt` varchar(100) DEFAULT NULL,
-
`locked` tinyint(1) DEFAULT '0',
-
-
UNIQUE KEY `idx_sys_users_username` (`username`)
-
)
ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;
-
-
-
-
-
-
-
locked 鎖定 默認爲0(false)表示沒有鎖
用戶表User:app
-
-
-
import org.springframework.util.CollectionUtils;
-
import org.springframework.util.StringUtils;
-
-
import java.io.Serializable;
-
import java.util.ArrayList;
-
-
-
public class User implements Serializable {
-
private static final long serialVersionUID = -651040446077267878L;
-
-
-
private Long organizationId;
-
-
-
-
private List<Long> roleIds;
-
private Boolean locked = Boolean.FALSE;
-
-
-
-
-
public User(String username, String password) {
-
this.username = username;
-
this.password = password;
-
-
-
-
-
-
-
public void setId(Long id) {
-
-
-
-
public Long getOrganizationId() {
-
-
-
-
public void setOrganizationId(Long organizationId) {
-
this.organizationId = organizationId;
-
-
-
public String getUsername() {
-
-
-
-
public void setUsername(String username) {
-
this.username = username;
-
-
-
public String getPassword() {
-
-
-
-
public void setPassword(String password) {
-
this.password = password;
-
-
-
public String getSalt() {
-
-
-
-
public void setSalt(String salt) {
-
-
-
-
-
public String getCredentialsSalt() {
-
-
-
-
public List<Long> getRoleIds() {
-
-
roleIds = new ArrayList<
Long>();
-
-
-
-
-
public void setRoleIds(List<Long> roleIds) {
-
-
-
-
-
public String getRoleIdsStr() {
-
if(CollectionUtils.isEmpty(roleIds)) {
-
-
-
StringBuilder s = new StringBuilder();
-
for(Long roleId : roleIds) {
-
-
-
-
-
-
-
public void setRoleIdsStr(String roleIdsStr) {
-
if(StringUtils.isEmpty(roleIdsStr)) {
-
-
-
String[] roleIdStrs = roleIdsStr.split(
",");
-
for(String roleIdStr : roleIdStrs) {
-
if(StringUtils.isEmpty(roleIdStr)) {
-
-
-
getRoleIds().add(
Long.valueOf(roleIdStr));
-
-
-
-
public Boolean getLocked() {
-
-
-
-
public void setLocked(Boolean locked) {
-
-
-
-
-
public boolean equals(Object o) {
-
if (this == o) return true;
-
if (o == null || getClass() != o.getClass()) return false;
-
-
-
-
if (id != null ? !id.equals(user.id) : user.id != null) return false;
-
-
-
-
-
-
-
return id != null ? id.hashCode() : 0;
-
-
-
-
public String toString() {
-
-
-
", organizationId=" + organizationId +
-
", username='" + username + '\'' +
-
", password='" + password + '\'' +
-
", salt='" + salt + '\'' +
-
-
-
-
-
-------------------------------------------------------------------------------------------加密----------------------------------------------
正如前面散列算法的說法:加密採用的是MD5或者SHA算法和salt鹽結合產生不可逆的加密。
什麼是鹽?
拋開鹽不說:
例如用戶名admin 密碼123,經過md5加密密碼獲得新的密碼值爲21232f297a57a5a743894a0e4a801fc3,這樣經過數字字典很容易就知道md5加密後的密碼爲123.
若加入一些系統已經知道的干擾數據,這些干擾的數據就是鹽。則密碼就是由 sale(鹽) + 經過鹽生成的密碼組成,這樣同一個密碼加密生成的密碼是各不相同的達到不可逆加密。
對密碼進行鹽加密的工具:
這個是jdbc.properties配置文件,裏面有shiro加密中須要配的算法名稱和迭代次數。算法名稱能夠爲md5,sha-1,sha-256.
若填的算法名稱不是加密算法如aaa,則會報錯:Caused by: java.security.NoSuchAlgorithmException: abc MessageDigest not available
-
-
connection.url=jdbc:mysql:
-
-
-
-
-
-
-
-
-
druid.timeBetweenEvictionRunsMillis=
60000
-
druid.minEvictableIdleTimeMillis=
300000
-
druid.validationQuery=SELECT
'x'
-
druid.testWhileIdle=
true
-
druid.testOnBorrow=
false
-
druid.testOnReturn=
false
-
druid.poolPreparedStatements=
true
-
druid.maxPoolPreparedStatementPerConnectionSize=
20
-
-
-
-
password.algorithmName=sha
-1
-
password.hashIterations=
2
密碼加密工具類:
-
-
-
import org.apache.shiro.crypto.RandomNumberGenerator;
-
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
-
import org.apache.shiro.crypto.hash.SimpleHash;
-
import org.apache.shiro.util.ByteSource;
-
import org.springframework.beans.factory.annotation.Value;
-
import org.springframework.stereotype.Service;
-
-
import com.lgy.model.User;
-
-
-
public class PasswordHelper {
-
-
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
-
-
@Value("${password.algorithmName}")
-
private String algorithmName;
-
@Value("${password.hashIterations}")
-
private int hashIterations;
-
-
public void encryptPassword(User user) {
-
-
user.setSalt(randomNumberGenerator.nextBytes().toHex());
-
-
String newPassword =
new SimpleHash(
-
-
-
ByteSource.Util.bytes(user.getCredentialsSalt()),
-
-
-
-
user.setPassword(newPassword);
-
-
密碼中干擾的值是username+salt組成, salt是用RandomNumberGererator隨機生成的值。能夠自定義,也能夠不須要salt這個字段。這樣在數據庫中生成的數據有:
一樣的密碼123456,獲得的密碼值是不同的!
用戶名 密碼 鹽值
admin c4270458aca71740949bead254d6e9fb 228723e1ecce4511f2ff3a02a1a6a57b
feng 2053ad769d326bc6b36f97aac53b72a6a cf12465e22601b8399439e526499f5c
---------------------------------------------------------------------------解密-----------------------------------------------------------------
shiro框架的解密是經過:HashedCredentialsMatcher實現密碼驗證服務
a.首先配置本身的realm:
-
-
<bean id="userRealm" class="com.lgy.realm.UserRealm">
-
-
<property name="credentialsMatcher" ref="credentialsMatcher"/>
-
<property name="cachingEnabled" value="false"/>
-
-
-
-
-
-
-
<bean id="credentialsMatcher" class="com.lgy.credentials.RetryLimitHashedCredentialsMatcher">
-
<constructor-arg ref="cacheManager"/>
-
<property name="hashAlgorithmName" value="sha-1"/>
-
<property name="hashIterations" value="2"/>
-
<property name="storedCredentialsHexEncoded" value="true"/>
-
密碼驗證方式是自定義實現的,RetryLimitHashedCredentialsMatcher實現類以下:
-
package com.lgy.credentials;
-
-
import org.apache.shiro.authc.AuthenticationInfo;
-
import org.apache.shiro.authc.AuthenticationToken;
-
import org.apache.shiro.authc.ExcessiveAttemptsException;
-
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
-
import org.apache.shiro.cache.Cache;
-
import org.apache.shiro.cache.CacheManager;
-
-
import java.util.concurrent.atomic.AtomicInteger;
-
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
-
-
private Cache<String, AtomicInteger> passwordRetryCache;
-
-
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
-
passwordRetryCache = cacheManager.getCache(
"passwordRetryCache");
-
-
-
-
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
-
String username = (String)token.getPrincipal();
-
-
AtomicInteger retryCount = passwordRetryCache.get(username);
-
-
retryCount =
new AtomicInteger(0);
-
passwordRetryCache.put(username, retryCount);
-
-
if(retryCount.incrementAndGet() > 5) {
-
-
throw new ExcessiveAttemptsException();
-
-
-
boolean matches = super.doCredentialsMatch(token, info);
-
-
-
passwordRetryCache.remove(username);
-
-
-
-
這裏要注意認證憑證中的2個參數值的設置要與加密時的一致,分別是算法名稱)和迭代次數.
userRealm類以下:
-
-
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
-
String username = (String)token.getPrincipal();
-
User user = userService.findByUsername(username);
-
-
throw new UnknownAccountException();
-
-
if(Boolean.TRUE.equals(user.getLocked())) {
-
throw new LockedAccountException();
-
-
-
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(
-
-
-
ByteSource.Util.bytes(user.getCredentialsSalt()),
-
-
-
return authenticationInfo;
-
經過SimpleAuthenticationInfo將鹽值以及用戶名和密碼信息封裝到AuthenticationInfo中,進入證書憑證類中進行校驗。