本文主要引入的jar包如下:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
由于我们将使用shiro + ehache配合使用,所以可以不用单独再引用ehcache.jar了,使用shiro-ehcache时,会自动添加ehcache-core 2.6.11。
resources config ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false"
dynamicConfig="true"
name="ehcache_retry">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache" />
<!-- 默认缓存 -->
<defaultCache maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap" />
</defaultCache>
<!-- helloworld缓存 -->
<cache name="passwordRetryCache"
maxElementsInMemory="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
overflowToDisk="false"
statistics="true" />
<!--
设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
Hibernate 在不同的缓存区域保存不同的类/集合。
对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders
-->
<!--
name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目
eternal: 设置对象是否为永久的, true表示永不过期, 此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
-->
</ehcache>
关于以上属性代表的含义,可以在官方文档中找到官方文档 配置中不能对ehcache标签添加monitoring=“autodetect”,否侧缓存将无法保存。
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
//给当前realm起个名
@Override
public String getName() {
return "customReam02";
}
//支持UsernamePasswordToken
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户主身份---用户名
String username = (String) principalCollection.getPrimaryPrincipal();
//通过用户名查找用户对应的权限列表
List<SysPermission> permissionList = sysUserService.findPermission(username);
//创建一个授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for(SysPermission sysPermission:permissionList){
authorizationInfo.addStringPermission(sysPermission.getPercode());
}
return authorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取身份
String username = (String) authenticationToken.getPrincipal();
//通过用户名,查找对应的用户是否存在,如果存在返回用户对象
SysUser sysUser = sysUserService.findUser(username);
if(sysUser == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
sysUser.getUsercode(), //用户名
sysUser.getPassword(), //密码
ByteSource.Util.bytes(sysUser.getSalt()),//salt
getName() //realm name
);
return authenticationInfo;
}
}
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher{
private static final int MAX_LOGIN_RETRY_TIMES = 5;
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitCredentialsMatcher(EhCacheManager ehCacheManager) {
passwordRetryCache = ehCacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws ExcessiveAttemptsException{
String userName = (String) token.getPrincipal();
AtomicInteger retryCount = passwordRetryCache.get(userName);
if (retryCount == null) {
// 高并发下使用的线程安全的int类
retryCount = new AtomicInteger(0);
passwordRetryCache.put(userName, retryCount);
}
if (retryCount.incrementAndGet() > MAX_LOGIN_RETRY_TIMES) {
throw new ExcessiveAttemptsException();
}
boolean match = super.doCredentialsMatch(token, info);
if (match) {
passwordRetryCache.remove(userName);
}
return match;
}
}
这个类的主要作用就是计算并缓存用户尝试登陆的次数,如果大于了5次,那么该用户将被禁止登陆直到10分钟以后。这个时间在ehcache.xml中timeToIdleSeconds设置。
//配置文件注解
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//filterChainDefinitionMap.put("/userInfo/userList", "userInfo:view");
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
//myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
//匹配新创建管理匹配器
myShiroRealm.setCredentialsMatcher(retryLimitCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
return ehCacheManager;
}
@Bean
public CredentialsMatcher retryLimitCredentialsMatcher() {
RetryLimitCredentialsMatcher retryLimitCredentialsMatcher = new RetryLimitCredentialsMatcher(this.ehCacheManager());
retryLimitCredentialsMatcher.setHashAlgorithmName("md5");
retryLimitCredentialsMatcher.setHashIterations(2);
retryLimitCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return retryLimitCredentialsMatcher;
}
}
@Component
public class SpringEhcacheShutdownListener implements ApplicationListener<ApplicationEvent>{
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
ApplicationContext context = ((ContextClosedEvent) event).getApplicationContext();
EhCacheManager ehCacheManager = (EhCacheManager) context.getBean("ehCacheManager");
if (ehCacheManager != null) {
ehCacheManager.destroy();
}
}
}
}
配置每次登出或者重启之后,清空缓存
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
System.out.println("HomeController.login()");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
String msg = "";
if (exception!= null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -- > 账号不存在:");
msg = "UnknownAccountException -- > 账号不存在:";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "IncorrectCredentialsException -- > 密码不正确:";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "kaptchaValidateFailed -- > 验证码错误";
}else if (ExcessiveAttemptsException.class.getName().equals(exception)) {
System.out.println("ExcessiveAttemptsException -- > 密码错误次数过多,已被锁定,请稍后重试");
msg = "ExcessiveAttemptsException -- > 密码错误次数过多,已被锁定,请稍后重试";
} else {
msg = "else >> "+exception;
System.out.println("else -- >" + exception);
}
}
map.put("msg", msg);
// 此方法不处理登录成功,由shiro进行处理
return "login";
}
@RequestMapping("/403")
public String unauthorizedRole(){
System.out.println("------没有权限-------");
return "403";
}
}
连续登陆你设置的次数,就会报登录次数太多