浅谈Shiro框架中的加密算法,以及校验
在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。为什么要加密:网络安全问题是一个很大的隐患,用户数据泄露事件层出不穷,比如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 configure
- connection.url=jdbc:mysql://localhost:3306/shiro-demo
- connection.username=root
- connection.password=
- #druid datasource
- druid.initialSize=10
- druid.minIdle=10
- druid.maxActive=50
- druid.maxWait=60000
- 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
- druid.filters=wall,stat
- #shiro
- password.algorithmName=sha-1
- password.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;
- @Service
- 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(
- 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框架中的加密算法,以及校验的更多相关文章
- 2014-07-29 浅谈MVC框架中Razor与ASPX视图引擎
今天是在吾索实习的第15天.随着准备工作的完善,我们小组将逐步开始手机端BBS的开发,而且我们将计划使用MVC框架进行该系统的开发.虽然我们对MVC框架并不是非常熟悉,或许这会降低我们开发该系统的效率 ...
- 浅谈关于QT中Webkit内核浏览器
关于QT中Webkit内核浏览器是本文要介绍的内容,主要是来学习QT中webkit中浏览器的使用.提起WebKit,大家自然而然地想到浏览器.作为浏览器内部的主要构件,WebKit的主要工作是渲染.给 ...
- 手撸ORM浅谈ORM框架之基础篇
好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...
- 手撸ORM浅谈ORM框架之Add篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 手撸ORM浅谈ORM框架之Update篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 手撸ORM浅谈ORM框架之Delete篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 手撸ORM浅谈ORM框架之Query篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 转: 浅谈C/C++中的指针和数组(二)
转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...
- 转:浅谈C/C++中的指针和数组(一)
再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...
随机推荐
- Java Thread(线程)案例详解sleep和wait的区别
上次对Java Thread有了总体的概述与总结,当然大多都是理论上的,这次我将详解Thread中两个常用且容易疑惑的方法.并通过实例代码进行解疑... F区别 sleep()方法 sleep()使当 ...
- Python占位符使用总结
格式化对象为字符串:%s myName=input('Enter your name:') userAge=input('Enter your age:') userHight=input('Ente ...
- docker-compose deploy replicaSet in standalone MongoDB cluster and with auth
经过两天的折腾,终于实现了自己想要的效果,就是通过docker-compose 部署最新的mongodb replicaSet, 主要是为了测试 4.2 最新的多文档事务,下面将整个步骤分享一下: d ...
- Java秒杀实战 (四)JMeter压测
转自:https://blog.csdn.net/qq_41305266/article/details/81071278. 一.JMeter入门 下载链接 http://jmeter.apache. ...
- Dart运算符与语句格式
算术运算符 + 加 - 减 * 乘 / 除 ~/ 取整 % 取余 关系运算符 == 是否等于 != 是否不等于 > 是否大于 < 是否小于 >= 是否大于等于 <= 是否小于等 ...
- 01_Hive简介及其工作机制
1.Hive简介 Hive是一个基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一个表.并提供类SQL查询功能, 可以将sql语句转换为MapReduce任务运行.其优点是学习成本低, ...
- 团队第三次作业:Alpha版本第二周小结
姓名 学号 周前计划安排 每周实际工作记录 自我打分 XXX 061109 1.对原型设计与编码任务进行进一步的规划与任务分配 2.协调与统一已完成的部分原型设计页面风格并针对部分页面提出了改进建议 ...
- Matlab---绘图及其位置摆放
Matlab---绘图及其位置摆放 [@WP@20180509] 一.绘图函数 (1)绘制二维图形 (1.1) plot( ) 函数的应用格式. 1,plot(x). 当x 为一向量时,以x 元素的值 ...
- windows下通过批处理脚本启动redis
三种启动方式的特点: 第一种方式 :根目录之命令窗口启动 特点:每次启动都要进入到redis的根目录,比较繁琐,并且占用一个窗口 第二种方式:bat脚本便捷启动 特点:可放在桌面便捷启动,占用窗口 第 ...
- Tarjan-CV/BCC/SCC算法学习笔记
DFS性质的应用--利用Tarjan算法求割顶.BCC.SCC 整理自<算法竞赛入门经典--训练指南>以及网络 DFS (depth first search)深度优先搜索算法 dfs森林 ...