在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。为何要加密:网络安全问题是一个很大的隐患,用户数据泄露事件层出不穷,好比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中,进入证书凭证类中进行校验。