用Shiro做登录权限控制时,密码加密是自定义的。

数据库的密码通过散列获取,如下,算法为:md5,盐为一个随机数字,散列迭代次数为3次,最终将salt与散列后的密码保存到数据库内,第二次登录时将登录的令牌再进行同样的运算后再与数据库的做对比。

String algorithmName = "md5";
String userName = "rose";
String password = "rose123";
int hashIterations = 3; //散列迭代次数
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
// 将用户的密码经过散列算法替换成一个不可逆的新密码保存进数据,散列过程使用了盐
SimpleHash simpleHash = new SimpleHash(algorithmName,password,userName+salt,hashIterations);
String encodedPassword = simpleHash.toHex();
System.out.println("salt is "+salt);
System.out.println("encodedPassword is "+encodedPassword);

创建RetryLimitHashedCredentialsMatcher类,此类有登录失败次数的判断,多于5次后再等待10分钟后才能重试。

缓存机制用到了Ehcache,Ehcache是很多Java项目中使用的缓存框架,Hibernate就是其中之一。它的本质就是将原本只能存储在内存中的数据通过算法保存到硬盘上,再根据需求依次取出。你可以把Ehcache理解为一个Map<String,Object>对象,通过put保存对象,再通过get取回对象。

package com.ken.shiro;

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 = token.getPrincipal().toString();
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
System.out.println(userName);
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
AtomicInteger retryCount = passwordRetryCache.get(userName);
if (null == retryCount) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(userName,retryCount);
} if (retryCount.incrementAndGet() > 5) {
throw new ExcessiveAttemptsException();
} boolean matches = super.doCredentialsMatch(token, info); if (matches){
passwordRetryCache.remove(userName);
} return matches;
}
}

spring-shiro.xml内加入配置

<!-- 数据库保存的密码是使用MD5算法加密的,所以这里需要配置一个密码匹配对象 -->
<!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>-->
<bean id="credentialsMatcher" class="com.ken.shiro.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"></constructor-arg>
<property name="hashAlgorithmName" value="md5"></property><!--加密算法为md5-->
<property name="hashIterations" value="3"></property><!--3次md5迭代-->
<!--是否存储散列后的密码为16进制,需要和生成密码时的一样,默认是base64-->
<property name="storedCredentialsHexEncoded" value="true"></property>
</bean>
<!--缓存管理-->
<!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.MemoryConstrainedCacheManager"></bean>-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" >
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>

ehcache.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shirocache"> <diskStore path="java.io.tmpdir" /> <!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
--> <defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/> <!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
eternal="false"
maxElementsInMemory="0"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> <cache name="authorizationCache" eternal="false" maxElementsInMemory="0"
timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
statistics="true">
</cache> <cache name="authenticationCache" eternal="false" maxElementsInMemory="0"
timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
statistics="true">
</cache> <cache name="shiro-activeSessionCache" eternal="false" maxElementsInMemory="0"
timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
statistics="true">
</cache> </ehcache>

  

实现Realm类MyShiro继承自AuthorizingRealm,AuthorizingRealm实现它的抽象方法doGetAuthorizationInfo权限角色进行配置,AuthorizingRealm又继承自AuthenticatingRealm,AuthenticatingRealm也有一个抽象方法doGetAuthenticationInfo,实现doGetAuthenticationInfo方法对登录的令牌等信息进行验证。

myShiro的职则是对登录进行授权,对角色、权限进行验证等。

package com.ken.service.impl;

import com.ken.entity.TRole;
import com.ken.entity.TUser;
import com.ken.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List; @Service
public class MyShiro extends AuthorizingRealm { @Autowired
IUserService userService; @Autowired //注入父类的属性,注入加密算法匹配密码时使用
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher){
super.setCredentialsMatcher(credentialsMatcher);
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("######################");
System.out.println("realm==="+this.getName());
System.out.println("######################"); //获取登录时输入的用户名
String loginName = (String)principalCollection.fromRealm(getName()).iterator().next();
//获取当前的用户名,跟上面的一样
String currentUsername = (String)super.getAvailablePrincipal(principalCollection);
System.out.println(currentUsername); TUser user = userService.findUserByName(loginName);
if (user != null) {
//权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //用户的角色集合
simpleAuthorizationInfo.setRoles(user.getRolesName()); //对应角色的权限
List<TRole> roles = user.getRoles();
for (TRole role:roles){
simpleAuthorizationInfo.addStringPermissions(role.getPermissionName());
} return simpleAuthorizationInfo;
}
return null;
} //如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException实现。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; System.out.println("AuthenticationInfo####################");
System.out.println(((UsernamePasswordToken) authenticationToken).getUsername()); String pwd = new String(((UsernamePasswordToken) authenticationToken).getPassword()); System.out.println("getPassword="+pwd); System.out.println(authenticationToken.getPrincipal().toString()); String password = new String((char[])authenticationToken.getCredentials()); //得到密码 System.out.println(password); this.setSession("currentUser",authenticationToken.getPrincipal().toString()); TUser user = userService.findUserByName(token.getUsername());
if (user != null) {
System.out.println("user salt is "+user.getCredentialsSalt());
//这里获取到数据库的用户名密码,然后验证用户名密码,如果不对则执出异常
return new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()) //获取盐
,getName()); }
return null;
} /**
* 将一些数据放到ShiroSession中,以便于其它地方使用
* 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
*/
private void setSession(Object key,Object value){
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
if (null != session) {
session.setAttribute(key,value);
}
}
}
}

完整的spring-shiro.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置权限管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- ref对应我们写的realm MyShiro -->
<property name="realm" ref="myShiro"></property>
<property name="cacheManager" ref="cacheManager"></property>
</bean> <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 调用我们配置的权限管理器 -->
<property name="securityManager" ref="securityManager"></property>
<property name="loginUrl" value="/login"></property>
<!-- 配置我们在登录页登录成功后的跳转地址,如果你访问的是非/login地址,则跳到您访问的地址 -->
<property name="successUrl" value="/user"></property>
<!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 -->
<property name="unauthorizedUrl" value="/403"></property>
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- anon表示此地址不需要任何权限即可访问 refer to:http://blog.csdn.net/jadyer/article/details/12172839 -->
/static/**=anon
/verifyImage=anon
<!-- perms[user:query]表示访问此连接需要权限为user:query的用户 -->
/user=perms[query]
<!-- roles[manager]表示访问此连接需要用户的角色为manager -->
/user/add=roles[manager]
/user/del/**=roles[admin]
/user/edit/**=roles[manager]
<!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login-->
/** = authc
</value>
</property>
</bean> <!-- 数据库保存的密码是使用MD5算法加密的,所以这里需要配置一个密码匹配对象 -->
<!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>-->
<bean id="credentialsMatcher" class="com.ken.shiro.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"></constructor-arg>
<property name="hashAlgorithmName" value="md5"></property><!--加密算法为md5-->
<property name="hashIterations" value="3"></property><!--3次md5迭代-->
<!--是否存储散列后的密码为16进制,需要和生成密码时的一样,默认是base64-->
<property name="storedCredentialsHexEncoded" value="true"></property>
</bean> <!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <!--缓存管理-->
<!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.MemoryConstrainedCacheManager"></bean>-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" >
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean> </beans>

  

Shiro自定义Realm时用注解的方式注入父类的credentialsMatcher的更多相关文章

  1. shiro自定义realm支持MD5算法认证(六)

    1.1     散列算法 通常需要对密码 进行散列,常用的有md5.sha, 对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文. 建议对md5进行散列时加salt(盐),进行 ...

  2. 权限框架 - shiro 自定义realm

    上篇文章中是使用的默认realm来实现的简单登录,这仅仅只是个demo,真正项目中使用肯定是需要连接数据库的 首先创建自定义realm文件,如下: 在shiro中注入自定义realm的完全限定类名: ...

  3. shiro自定义Realm

    1.1 自定义Realm 上边的程序使用的是shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm. ...

  4. shiro自定义realm认证(五)

    上一节介绍了realm的作用: realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null.token就相当于是对用户输入 ...

  5. (3)shiro自定义realm

    上面一章说到shiro的认证和授权最底层就是调用realm的getAuthorizationInfo(获取用户的角色和资源)和getAuthenticationInfo(校验账号密码是否正确)两个方法 ...

  6. Shiro自定义realm实现密码验证及登录、密码加密注册、修改密码的验证

    一:先从登录开始,直接看代码 @RequestMapping(value="dologin",method = {RequestMethod.GET, RequestMethod. ...

  7. 将spring管理的bean使用注解的方式注入到servlet中

    Filter和Servlet中不能直接注解使用spring的bean,因为这两个都是servlet容器维护管理的,当然也有实现方法,如下: 1.创建一个AbstractServlet 抽象类,让你的所 ...

  8. spring使用注解通过子类注入父类的私有变量

    方法一 通过 super.setBaseDao方法设置父类私有变量 父类 public class BaseServiceImpl {    private BaseDao baseDao; publ ...

  9. Java-Shiro(五):Shiro Realm讲解(二)IniRealm的用法、JdbcRelam的用法、自定义Realm

    引入 上一篇在讲解Realm简介时,介绍过Realm包含大概4类缺省的Realm,本章主要讲解: 1)IniRealm的用法: 2)JdbcRealm基于mysql   默认表及查询语句实现认证.授权 ...

随机推荐

  1. Ant Design 日期选择组件RangePicker 选择时间范围后计算范围内的天数。

    /** *需求:同年同月,同年不同月(两个月相减大于1,小于1),不同年(两个年相减大于1(是否为闰年),小于1),起止包含的月份及天 */ //首先引入组件 import { DatePicker} ...

  2. Highcharts 配置选项详细说明

    Highcharts 配置选项详细说明 Highcharts 提供大量的配置选项参数,您可以轻松定制符合用户要求的图表,本章节为大家详细介绍Highcharts 配置选项使用说明: 参数配置(属性+事 ...

  3. UI基础七:给普通其他界面的PRODUCT 添加标准的搜索帮助

    在使用的组件中添加组件对象 Outbound Plug中添加外向连接:OP_PRODUCT METHOD op_product. DATA: lv_title TYPE string, lr_cont ...

  4. java前后向查找个人理解

    举一个最简单的栗子 这个前后说的是0宽所在的位置,是在:前还是后 http://www.sb.com 1.前向正向查找 (1) 如果用:.*(?=:) 首先(?=:)被称作0宽度断言,所谓0宽度应该是 ...

  5. windows 系统使用 git 和码云管理代码(本地已有项目)

    1. 为本地项目创建本地仓 找到项目所在的根目录(目录下有解决方案那个),右击目录,点击右键菜单中的“Git Bash Here”(前提是你的电脑已经装了Git,我用的是TortoiseGit) 然后 ...

  6. 【转】分享前端开发中通过js设置/获取cookie的一组方法

    在前端开发中,通常都需要获取并记录用户的某些操作设置,这样可以使用户下一次访问网站时不用进行重复的调整设置同一个功能. js方法的完整代码如下: var cookie = { set:function ...

  7. 【LeetCode】数值运算(除法、乘方)

    C/C++数字范围(32位系统) ~ // 1 字节 char // 1 字节 ~ // 2 字节 - // 4 字节 unsigned: - // 4 字节 size_t: ~ // 4 字节 - ...

  8. 一、SQLite学习

    由于公司业务拓展,需要开发一个基于ASP.NET Core2.0+AdminLTE架构的后台管理系统,数据库选择方面,选择了使用SQLite轻便. SQLite:一个软件库,实现自给自足,无服务器,零 ...

  9. PhpDocumentor 生成文档

    最近项目需要phpdoc生成文档,首先安装PhpDocumentor,利用pear安装: 切换用户: su root 安装PhpDocumentor: pear install PhpDocument ...

  10. 使用AndroidStudio导入github项目

    1.在studio中配置github的项目地址 2.当你点击github,会这个样子 3.此处放你要clone的地址 ,然后点击clone. 4.等一会会出现这个页面,然后点击yes ,会出现这个页面 ...