用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. 5月21 汽车查询及批量删除----php方法

    ---恢复内容开始--- 这个与之前不同是在php中实现了页面的查询,引用AJAX实现批量删除及弹窗的显示 作业要求: 页面显示数据代码: <!DOCTYPE html PUBLIC " ...

  2. leetcode-algorithms-26 Remove Duplicates from Sorted Array

    leetcode-algorithms-26 Remove Duplicates from Sorted Array Given a sorted array nums, remove the dup ...

  3. Spring注解之@Import

    /** * Indicates one or more {@link Configuration @Configuration} classes to import. * 表示import 一个或多个 ...

  4. ajax请求二进制流图片并渲染到html中img标签

    日常显示图片都诸如这种形式:直接使用img的src属性 <img src="图片路径.地址" alt="" /> 以上方法无法在获取图片请求中设置请 ...

  5. 数据库恢复(database restore)之兵不血刃——半小时恢复客户数据库

    昨天,一个客户打打来电话,说他们的数据库坏了,不能用了,需要我帮助恢复下,这马上要放假了,居然出了这事儿,自己也不太喜欢恢复数据库这类,尤其是他们的数据库是个win上的库,但心里很清楚,客户比咱着急, ...

  6. 【转】C# string数组转int数组

    //字符串数组(源数组) string[] sNums = new[] {"1", "2"}; //整型数组(目标数组) int[] iNums; //转换方法 ...

  7. kali-rolling安装openvas 9并创建扫描任务教程

    一. 前置说明 官方:漏洞扫描中最常用和强大的是某个”N“开头的漏洞扫描器(nessus),但由于许可证限制,kali中并未安装该漏洞扫描器.取而代之安装了nessus收费之后发起的开源版本openv ...

  8. JDBC、JNDI和DBCP的区别

    JDBC:Java DataBase Connectivity,java连接数据库和执行SQL语句的API. 数据源:Data Source.就是将IP.数据库.用户名.密码封装起来对外只提供一个JN ...

  9. listener.ora和tnsnames.ora格式解析

    listener.ora是oracle数据库服务端的监听配置文件,包括协议.IP地址和端口等内容:tnsnames.ora是oracle数据库客户端的连接配置文件,也是对应的协议.IP地址和端口等内容 ...

  10. npm 安装nodesass 或者包含nodesass的脚手架工具报错问题

    由于最近vue转angular 但是angular版本太多了,好多项目是angularv4 有的是v5 近日angular又发布了v6,依赖的东西好多不一样,结果npm install 时候,总是出现 ...