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

    解释 GIL 全局解释器锁 def func(*args): for i in args: print(i) func(3,2,1,4,7) 在我们不知道该传递多少关键字参数时,使用**kwargs ...

  2. str.charAt()与str[]的区别

    str.charAt():只能显示当前字符,没有则显示空. str[]:当索引超出当前字符长度时,则显示undefined.

  3. centos7 firewall指定IP与端口、端段访问(常用)

    https://blog.csdn.net/yipianfuyunsm/article/details/99998332 https://www.cnblogs.com/co10rway/p/8268 ...

  4. 学习前端 第一天之html标签补充

    一.常用浏览器内核 Trident(IE内核) Gecko(firefox) webkit(Safari) Chromium/Blink(chrome) Blink(Opera) 二.常见标签回顾 a ...

  5. case when语法

    Case具有两种格式.简单Case函数和Case搜索函数. --简单Case函数: CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END ...

  6. WinPE基础知识之重定位表

    typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; // (重要)需要重定位的位置,这是一个RVA DWORD SizeOfBl ...

  7. nodejs request module里的json参数的一个坑

    今天工作的时候遇到一个坑,在客户端用nodejs给服务器发送HTTP请求,服务器老是报错:In the context of Data Services an unknown internal ser ...

  8. 深入SpringBoot注解原理及使用

    首先,先看SpringBoot的主配置类: @SpringBootApplication public class StartEurekaApplication { public static voi ...

  9. Dart中的匿名方法与自执行方法

    void main() { // 匿名方法 var printSomethings = () { print("somethings"); }; printSomethings() ...

  10. (备忘)Window7下安装Python2.6及Django1.4

    1.Python2.6安装 1.1 Python2.6的下载及安装 Python 版本:2.6 下载地址:http://www.python.org/download/releases/2.6.1/  ...