浅谈Shiro框架中的加密算法,以及校验
来源:互联网 发布:sar算法平台 编辑:程序博客网 时间:2024/05/17 03:10
在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。为什么要加密:网络安全问题是一个很大的隐患,用户数据泄露事件层出不穷,比如12306账号泄露。
Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作,想了解自己百度API操作用法。
看一张图,了解Shiro提供的加密算法:
本文重点讲shiro提供的第二种:不可逆加密。
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
常见的算法有:MD5,SHA算法:
MD5算法是1991年发布的一项数字签名加密算法,它当时解决了MD4算法的安全性缺陷,成为应用非常广泛的一种算法。作为Hash函数的一个应用实例。
SHA诞生于1993年,全称是安全散列算法(Secure Hash Algorithm),由美国国家安全局(NSA)设计,之后被美国标准与技术研究院(NIST)收录到美国的联邦信息处理标准(FIPS)中,成为美国国家标准,SHA(后来被称作SHA-0)于1995被SHA-1(RFC3174)替代。SHA-1生成长度为160bit的摘要信息串,虽然之后又出现了SHA-224、SHA-256、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', PRIMARY KEY (`id`), UNIQUE KEY `idx_sys_users_username` (`username`)) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;说明:id主键字段 username 登录的用户名 passowrd 登录的密码 salt 盐 locked 锁定 默认为0(false)表示没有锁
用户表User:
package com.lgy.model;import org.springframework.util.CollectionUtils;import org.springframework.util.StringUtils;import java.io.Serializable;import java.util.ArrayList;import java.util.List;public class User implements Serializable { private static final long serialVersionUID = -651040446077267878L; private Long id; //编号 private Long organizationId; //所属公司 private String username; //用户名 private String password; //密码 private String salt; //加密密码的盐 private List<Long> roleIds; //拥有的角色列表 private Boolean locked = Boolean.FALSE; public User() { } public User(String username, String password) { this.username = username; this.password = password; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getOrganizationId() { return organizationId; } public void setOrganizationId(Long organizationId) { this.organizationId = organizationId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } //证书凭证 public String getCredentialsSalt() { return username + salt; } public List<Long> getRoleIds() { if(roleIds == null) { roleIds = new ArrayList<Long>(); } return roleIds; } public void setRoleIds(List<Long> roleIds) { this.roleIds = roleIds; } public String getRoleIdsStr() { if(CollectionUtils.isEmpty(roleIds)) { return ""; } StringBuilder s = new StringBuilder(); for(Long roleId : roleIds) { s.append(roleId); s.append(","); } return s.toString(); } public void setRoleIdsStr(String roleIdsStr) { if(StringUtils.isEmpty(roleIdsStr)) { return; } String[] roleIdStrs = roleIdsStr.split(","); for(String roleIdStr : roleIdStrs) { if(StringUtils.isEmpty(roleIdStr)) { continue; } getRoleIds().add(Long.valueOf(roleIdStr)); } } public Boolean getLocked() { return locked; } public void setLocked(Boolean locked) { this.locked = locked; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (id != null ? !id.equals(user.id) : user.id != null) return false; return true; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } @Override public String toString() { return "User{" + "id=" + id + ", organizationId=" + organizationId + ", username='" + username + '\'' + ", password='" + password + '\'' + ", salt='" + salt + '\'' + ", roleIds=" + roleIds + ", locked=" + locked + '}'; }}
-------------------------------------------------------------------------------------------加密----------------------------------------------
正如前面散列算法的说法:加密采用的是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
#dataSource configureconnection.url=jdbc:mysql://localhost:3306/shiro-democonnection.username=rootconnection.password=#druid datasourcedruid.initialSize=10druid.minIdle=10druid.maxActive=50druid.maxWait=60000druid.timeBetweenEvictionRunsMillis=60000druid.minEvictableIdleTimeMillis=300000druid.validationQuery=SELECT 'x'druid.testWhileIdle=truedruid.testOnBorrow=falsedruid.testOnReturn=falsedruid.poolPreparedStatements=truedruid.maxPoolPreparedStatementPerConnectionSize=20druid.filters=wall,stat#shiropassword.algorithmName=sha-1password.hashIterations=2
密码加密工具类:
package com.lgy.service;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;@Servicepublic 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( algorithmName, //加密算法 user.getPassword(), //密码 ByteSource.Util.bytes(user.getCredentialsSalt()), //salt盐 username + salt hashIterations //迭代次数 ).toHex(); user.setPassword(newPassword); }}
密码中干扰的值是username+salt组成, salt是用RandomNumberGererator随机生成的值。可以自定义,也可以不需要salt这个字段。这样在数据库中生成的数据有:
同样的密码123456,得到的密码值是不一样的!
用户名 密码 盐值
admin c4270458aca71740949bead254d6e9fb 228723e1ecce4511f2ff3a02a1a6a57b
feng 2053ad769d326bc6b36f97aac53b72a6a cf12465e22601b8399439e526499f5c
---------------------------------------------------------------------------解密-----------------------------------------------------------------
shiro框架的解密是通过:HashedCredentialsMatcher实现密码验证服务
a.首先配置自己的realm:
<!-- Realm实现 --> <bean id="userRealm" class="com.lgy.realm.UserRealm"> <!-- 密码验证方式 --> <property name="credentialsMatcher" ref="credentialsMatcher"/> <property name="cachingEnabled" value="false"/> <!--<property name="authenticationCachingEnabled" value="true"/>--> <!--<property name="authenticationCacheName" value="authenticationCache"/>--> <!--<property name="authorizationCachingEnabled" value="true"/>--> <!--<property name="authorizationCacheName" value="authorizationCache"/>--> </bean>
<!-- 凭证匹配器 --> <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"/> </bean>
密码验证方式是自定义实现的,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"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String)token.getPrincipal(); //retry count + 1 AtomicInteger retryCount = passwordRetryCache.get(username); if(retryCount == null) { retryCount = new AtomicInteger(0); passwordRetryCache.put(username, retryCount); } if(retryCount.incrementAndGet() > 5) { //if retry count > 5 throw throw new ExcessiveAttemptsException(); } boolean matches = super.doCredentialsMatch(token, info); if(matches) { //clear retry count passwordRetryCache.remove(username); } return matches; }}
这里要注意认证凭证中的2个参数值的设置要与加密时的一致,分别是算法名称)和迭代次数.
userRealm类如下:
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); User user = userService.findByUsername(username); if(user == null) { throw new UnknownAccountException();//没找到帐号 } if(Boolean.TRUE.equals(user.getLocked())) { throw new LockedAccountException(); //帐号锁定 } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getUsername(), //用户名 user.getPassword(), //密码 ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; }
通过SimpleAuthenticationInfo将盐值以及用户名和密码信息封装到AuthenticationInfo中,进入证书凭证类中进行校验。
- 浅谈Shiro框架中的加密算法,以及校验
- 浅谈Shiro框架中的加密算法,以及校验
- shiro安全框架扩展教程--设计数据对象校验器,如何防止xss以及csrf攻击
- shiro框架:shiro初体验以及原理
- Struts2中的校验框架
- Struts中的校验框架
- Struts2中的校验框架
- Struts2中的校验框架
- Struts2中的校验框架
- Struts2中的校验框架
- struts中的校验框架
- Struts2中的校验框架
- Struts2中的校验框架
- Struts2中的校验框架
- 浅谈shiro权限框架之认证过程
- 浅谈MD5加密算法中的加盐值(SALT)
- 浅谈MD5加密算法中的加盐值(SALT)
- 浅谈MD5加密算法中的加盐值(SALT)
- ifconfig: ioctl 0x8914 failed: Cannot assign requested address
- java中引用的原理
- 使用ReactiveCocoa实现iOS平台响应式编程
- Android 可拖拽的GridView效果实现, 长按可拖拽和item实时交换
- 陈力:传智播客古代 珍宝币 泡泡龙游戏开发第32讲:PHP 构造方法、析构方法
- 浅谈Shiro框架中的加密算法,以及校验
- ContentProvider(实现增生改查)(3)
- crontab 使用注意及处理No Space问题
- Java集合框架新特性
- ScrollView嵌套ListView解决办法
- django安装异常
- 获取线程池的方法 ExecutorService
- 陈力:传智播客古代 珍宝币 泡泡龙游戏开发第33讲:PHP 静态变量、静态方法
- Openstack nova代码部分注释一