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

    先说说最小路径覆盖的定义 定义:在一个有向图中,找出最少的路径,使得这些路径,经过每一个点,且每一个点只与一条路径相关联, 由上面得出: 1.一个单独的点是一个路径 2:如果有路径a,b,c....f ...

  2. 关键字:for_each

    std::for_each 先贴cppreference中对for_each的概述: template< class InputIt, class UnaryFunction > //此处 ...

  3. 【QT 学习笔记】 一、 VS2015+ QT环境安装

    1.   安装    qt-opensource-windows-x86-msvc2015_64-5.6.0.exe   (根据自己的VS版本来安装) 下载地址 http://download.qt. ...

  4. sip呼叫里SDP的一些字段的含义

    v=0 o=- 1 0 IN IP4 164.135.25.51 #local ip ,即本机SIP信令交互地址 s=SNS call #用于传递会话主题 c=IN IP4 164.135.25.51 ...

  5. 在Android8.0以上收不到广播问题(AppWidget)

    对Intent指定组件 //安卓8.0必须添加 intent.setComponent(new ComponentName(context,MyAppWidgetProvider.class)); 问 ...

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

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

  7. VScode 配置为 LaTeX 编辑器(IDE)

    VScode 配置为 LaTeX IDE 在Windows中,配置VScode作为LaTeX的编辑器(IDE),并使用SumatraPDF预览PDF文件.主要是LaTeX Workshop扩展的设置, ...

  8. java_八大数据类型

    一.整型 1.byte  1个字节(8位--一个字节占8位)-128~127 2.short  2个字节  -32768~32767 3.int      4个字节(常用) 4.long   8个字节 ...

  9. python django中的orm外键级联删除

    今天添加了一个路由表,路由表做外键,然后添加了几个组,路由表为组的外键,当我使用删除功能对路由表进行删除时,竞然将我的组也相当的删除了:尽管这是测试,但放到生产环境中还是会发生意外的:这个问题要解决: ...

  10. vim技巧总结

    自动补齐CTRL+N/CTRL+P vim 自动补全 颜色设置 hi Pmenu ctermfg=black ctermbg=gray guibg=#444444 hi PmenuSel ctermf ...