在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。为什么要加密:网络安全问题是一个很大的隐患,用户数据泄露事件层出不穷,比如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设计:

  1. CREATE TABLE `sys_users` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `username` varchar(100) DEFAULT NULL,
  4. `password` varchar(100) DEFAULT NULL,
  5. `salt` varchar(100) DEFAULT NULL,
  6. `locked` tinyint(1) DEFAULT '0',
  7. PRIMARY KEY (`id`),
  8. UNIQUE KEY `idx_sys_users_username` (`username`)
  9. ) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;
  10.  
  11.  
  12. 说明:id主键字段
  13. username 登录的用户名
  14. passowrd 登录的密码
  15. salt 盐
  16. locked 锁定 默认为0(false)表示没有锁

用户表User:

  1. package com.lgy.model;
  2.  
  3. import org.springframework.util.CollectionUtils;
  4. import org.springframework.util.StringUtils;
  5.  
  6. import java.io.Serializable;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9.  
  10. public class User implements Serializable {
  11. private static final long serialVersionUID = -651040446077267878L;
  12.  
  13. private Long id; //编号
  14. private Long organizationId; //所属公司
  15. private String username; //用户名
  16. private String password; //密码
  17. private String salt; //加密密码的盐
  18. private List<Long> roleIds; //拥有的角色列表
  19. private Boolean locked = Boolean.FALSE;
  20.  
  21. public User() {
  22. }
  23.  
  24. public User(String username, String password) {
  25. this.username = username;
  26. this.password = password;
  27. }
  28.  
  29. public Long getId() {
  30. return id;
  31. }
  32.  
  33. public void setId(Long id) {
  34. this.id = id;
  35. }
  36.  
  37. public Long getOrganizationId() {
  38. return organizationId;
  39. }
  40.  
  41. public void setOrganizationId(Long organizationId) {
  42. this.organizationId = organizationId;
  43. }
  44.  
  45. public String getUsername() {
  46. return username;
  47. }
  48.  
  49. public void setUsername(String username) {
  50. this.username = username;
  51. }
  52.  
  53. public String getPassword() {
  54. return password;
  55. }
  56.  
  57. public void setPassword(String password) {
  58. this.password = password;
  59. }
  60.  
  61. public String getSalt() {
  62. return salt;
  63. }
  64.  
  65. public void setSalt(String salt) {
  66. this.salt = salt;
  67. }
  68.  
  69. //证书凭证
  70. public String getCredentialsSalt() {
  71. return username + salt;
  72. }
  73.  
  74. public List<Long> getRoleIds() {
  75. if(roleIds == null) {
  76. roleIds = new ArrayList<Long>();
  77. }
  78. return roleIds;
  79. }
  80.  
  81. public void setRoleIds(List<Long> roleIds) {
  82. this.roleIds = roleIds;
  83. }
  84.  
  85.  
  86. public String getRoleIdsStr() {
  87. if(CollectionUtils.isEmpty(roleIds)) {
  88. return "";
  89. }
  90. StringBuilder s = new StringBuilder();
  91. for(Long roleId : roleIds) {
  92. s.append(roleId);
  93. s.append(",");
  94. }
  95. return s.toString();
  96. }
  97.  
  98. public void setRoleIdsStr(String roleIdsStr) {
  99. if(StringUtils.isEmpty(roleIdsStr)) {
  100. return;
  101. }
  102. String[] roleIdStrs = roleIdsStr.split(",");
  103. for(String roleIdStr : roleIdStrs) {
  104. if(StringUtils.isEmpty(roleIdStr)) {
  105. continue;
  106. }
  107. getRoleIds().add(Long.valueOf(roleIdStr));
  108. }
  109. }
  110.  
  111. public Boolean getLocked() {
  112. return locked;
  113. }
  114.  
  115. public void setLocked(Boolean locked) {
  116. this.locked = locked;
  117. }
  118.  
  119. @Override
  120. public boolean equals(Object o) {
  121. if (this == o) return true;
  122. if (o == null || getClass() != o.getClass()) return false;
  123.  
  124. User user = (User) o;
  125.  
  126. if (id != null ? !id.equals(user.id) : user.id != null) return false;
  127.  
  128. return true;
  129. }
  130.  
  131. @Override
  132. public int hashCode() {
  133. return id != null ? id.hashCode() : 0;
  134. }
  135.  
  136. @Override
  137. public String toString() {
  138. return "User{" +
  139. "id=" + id +
  140. ", organizationId=" + organizationId +
  141. ", username='" + username + '\'' +
  142. ", password='" + password + '\'' +
  143. ", salt='" + salt + '\'' +
  144. ", roleIds=" + roleIds +
  145. ", locked=" + locked +
  146. '}';
  147. }
  148. }

-------------------------------------------------------------------------------------------加密----------------------------------------------

正如前面散列算法的说法:加密采用的是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

  1. #dataSource configure
  2. connection.url=jdbc:mysql://localhost:3306/shiro-demo
  3. connection.username=root
  4. connection.password=
  5.  
  6. #druid datasource
  7. druid.initialSize=10
  8. druid.minIdle=10
  9. druid.maxActive=50
  10. druid.maxWait=60000
  11. druid.timeBetweenEvictionRunsMillis=60000
  12. druid.minEvictableIdleTimeMillis=300000
  13. druid.validationQuery=SELECT 'x'
  14. druid.testWhileIdle=true
  15. druid.testOnBorrow=false
  16. druid.testOnReturn=false
  17. druid.poolPreparedStatements=true
  18. druid.maxPoolPreparedStatementPerConnectionSize=20
  19. druid.filters=wall,stat
  20.  
  21. #shiro
  22. password.algorithmName=sha-1
  23. password.hashIterations=2

密码加密工具类:

  1. package com.lgy.service;
  2.  
  3. import org.apache.shiro.crypto.RandomNumberGenerator;
  4. import org.apache.shiro.crypto.SecureRandomNumberGenerator;
  5. import org.apache.shiro.crypto.hash.SimpleHash;
  6. import org.apache.shiro.util.ByteSource;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.stereotype.Service;
  9.  
  10. import com.lgy.model.User;
  11.  
  12. @Service
  13. public class PasswordHelper {
  14.  
  15. private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
  16.  
  17. @Value("${password.algorithmName}")
  18. private String algorithmName;
  19. @Value("${password.hashIterations}")
  20. private int hashIterations;
  21.  
  22. public void encryptPassword(User user) {
  23.  
  24. user.setSalt(randomNumberGenerator.nextBytes().toHex());
  25.  
  26. String newPassword = new SimpleHash(
  27. algorithmName, //加密算法
  28. user.getPassword(), //密码
  29. ByteSource.Util.bytes(user.getCredentialsSalt()), //salt盐 username + salt
  30. hashIterations //迭代次数
  31. ).toHex();
  32.  
  33. user.setPassword(newPassword);
  34. }
  35. }

密码中干扰的值是username+salt组成, salt是用RandomNumberGererator随机生成的值。可以自定义,也可以不需要salt这个字段。这样在数据库中生成的数据有:

同样的密码123456,得到的密码值是不一样的!

用户名                                    密码                                                              盐值

admin c4270458aca71740949bead254d6e9fb          228723e1ecce4511f2ff3a02a1a6a57b

feng 2053ad769d326bc6b36f97aac53b72a6a        cf12465e22601b8399439e526499f5c

---------------------------------------------------------------------------解密-----------------------------------------------------------------

shiro框架的解密是通过:HashedCredentialsMatcher实现密码验证服务

a.首先配置自己的realm:

  1. <!-- Realm实现 -->
  2. <bean id="userRealm" class="com.lgy.realm.UserRealm">
  3. <!-- 密码验证方式 -->
  4. <property name="credentialsMatcher" ref="credentialsMatcher"/>
  5. <property name="cachingEnabled" value="false"/>
  6. <!--<property name="authenticationCachingEnabled" value="true"/>-->
  7. <!--<property name="authenticationCacheName" value="authenticationCache"/>-->
  8. <!--<property name="authorizationCachingEnabled" value="true"/>-->
  9. <!--<property name="authorizationCacheName" value="authorizationCache"/>-->
  10. </bean>
  1. <!-- 凭证匹配器 -->
  2. <bean id="credentialsMatcher" class="com.lgy.credentials.RetryLimitHashedCredentialsMatcher">
  3. <constructor-arg ref="cacheManager"/>
  4. <property name="hashAlgorithmName" value="sha-1"/>
  5. <property name="hashIterations" value="2"/>
  6. <property name="storedCredentialsHexEncoded" value="true"/>
  7. </bean>

密码验证方式是自定义实现的,RetryLimitHashedCredentialsMatcher实现类如下:

  1. package com.lgy.credentials;
  2.  
  3. import org.apache.shiro.authc.AuthenticationInfo;
  4. import org.apache.shiro.authc.AuthenticationToken;
  5. import org.apache.shiro.authc.ExcessiveAttemptsException;
  6. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  7. import org.apache.shiro.cache.Cache;
  8. import org.apache.shiro.cache.CacheManager;
  9.  
  10. import java.util.concurrent.atomic.AtomicInteger;
  11. public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
  12.  
  13. private Cache<String, AtomicInteger> passwordRetryCache;
  14.  
  15. public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
  16. passwordRetryCache = cacheManager.getCache("passwordRetryCache");
  17. }
  18.  
  19. @Override
  20. public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
  21. String username = (String)token.getPrincipal();
  22. //retry count + 1
  23. AtomicInteger retryCount = passwordRetryCache.get(username);
  24. if(retryCount == null) {
  25. retryCount = new AtomicInteger(0);
  26. passwordRetryCache.put(username, retryCount);
  27. }
  28. if(retryCount.incrementAndGet() > 5) {
  29. //if retry count > 5 throw
  30. throw new ExcessiveAttemptsException();
  31. }
  32.  
  33. boolean matches = super.doCredentialsMatch(token, info);
  34. if(matches) {
  35. //clear retry count
  36. passwordRetryCache.remove(username);
  37. }
  38. return matches;
  39. }
  40. }

这里要注意认证凭证中的2个参数值的设置要与加密时的一致,分别是算法名称)和迭代次数.

userRealm类如下:

  1. @Override
  2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  3. String username = (String)token.getPrincipal();
  4. User user = userService.findByUsername(username);
  5. if(user == null) {
  6. throw new UnknownAccountException();//没找到帐号
  7. }
  8. if(Boolean.TRUE.equals(user.getLocked())) {
  9. throw new LockedAccountException(); //帐号锁定
  10. }
  11. //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
  12. SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
  13. user.getUsername(), //用户名
  14. user.getPassword(), //密码
  15. ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
  16. getName() //realm name
  17. );
  18. return authenticationInfo;
  19. }

通过SimpleAuthenticationInfo将盐值以及用户名和密码信息封装到AuthenticationInfo中,进入证书凭证类中进行校验。

浅谈Shiro框架中的加密算法,以及校验的更多相关文章

  1. 2014-07-29 浅谈MVC框架中Razor与ASPX视图引擎

    今天是在吾索实习的第15天.随着准备工作的完善,我们小组将逐步开始手机端BBS的开发,而且我们将计划使用MVC框架进行该系统的开发.虽然我们对MVC框架并不是非常熟悉,或许这会降低我们开发该系统的效率 ...

  2. 浅谈关于QT中Webkit内核浏览器

    关于QT中Webkit内核浏览器是本文要介绍的内容,主要是来学习QT中webkit中浏览器的使用.提起WebKit,大家自然而然地想到浏览器.作为浏览器内部的主要构件,WebKit的主要工作是渲染.给 ...

  3. 手撸ORM浅谈ORM框架之基础篇

    好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...

  4. 手撸ORM浅谈ORM框架之Add篇

    快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...

  5. 手撸ORM浅谈ORM框架之Update篇

    快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...

  6. 手撸ORM浅谈ORM框架之Delete篇

    快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...

  7. 手撸ORM浅谈ORM框架之Query篇

    快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...

  8. 转: 浅谈C/C++中的指针和数组(二)

    转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...

  9. 转:浅谈C/C++中的指针和数组(一)

    再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...

随机推荐

  1. 怎样在Chrome浏览器上安装 Vue Devtools 扩展程序

    第一步: 前往 GitHub 下载 Vue Devtools 项目文件 https://github.com/vuejs/vue-devtools 注意: 1. 将分支切换为 master 2. 下载 ...

  2. Java组合模式(思维导图)

    图1 组合模式[点击查看图片] 1,以公司职员为例的结构 package com.cnblogs.mufasa.demo3; import java.util.ArrayList; import ja ...

  3. vue中非父子组件的传值bus的使用

    非父子之间的组件传值,可以使用vuex.简单的状态管理,也可以用vue bus vue bus可以实现不同组件间.不同页面间的通信,比如我在A页面出发点击事件,要B页面发生变化,使用方法如下: 全局定 ...

  4. .NET 反射应用

    object request = null; string requestObjClassName = "命名空间" + 类型.ToString(); Type type = Ty ...

  5. FlowPortal BPM 安装环境的配置

    l  操作系统:Windows Server 2003 及以上: l  IIS: 在Internet信息服务(IIS)管理器中将ISAPI和CGI限制全部设为“允许” l  需要安装.Net Fram ...

  6. 使用SSH命令行远程登录运行在CloudFoundry上的应用

    当我试图用如下命令行采用SSH远程登录到运行在CloudFoundry环境下的应用时, cf ssh -N -T -L 9229:127.0.0.1:9229 jerry-demo-server 遇到 ...

  7. JPA中的主键生成策略

    通过annotation(注解)来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由@GeneratedValue设定的.这里的@id和@Gen ...

  8. SQL脚本优化

    1.创建索引一.要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引   (1)在经常需要进行检索的字段上创建索引,比如要按照表字段username进行检索,那么就应 ...

  9. Spark中资源与任务的关系

    在介绍Spark中的任务和资源之前先解释几个名词: Dirver Program:运行Application的main函数(用户提交的jar包中的main函数)并新建SparkContext实例的程序 ...

  10. 对于写Python学习笔记的看法

    学习写笔记是一个不错的学习方法,好些同学在学习Python过程中也会写学习笔记.俗话说好记性不如烂笔头,我很赞同这个说法. 我列举几个学习Python写笔记的好处: 1.Python知识的二度巩固 通 ...