利用Ehcache和Shiro实现 密码重试次数限制

1.添加依赖

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.6</version>
        </dependency>
    </dependencies>

2.配置Ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

    <diskStore path="java.io.tmpdir"/>

    <!-- 登录记录缓存 锁定10分钟 -->
    <cache name="passwordRetryEhcache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>
</ehcache>

3.编写自定义的CredentialsMatcher

package com.github.zhangkaitao.shiro.chapter5.credentials;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
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 java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 杨郑耀
 * @description
 * @create 2019-06-14-9:26
 */
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

	private Ehcache passwordRetryEhcache;

	public RetryLimitHashedCredentialsMatcher(){
		CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
		passwordRetryEhcache = cacheManager.getCache("passwordRetryEhcache");
	}
	@Override
	public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
		//1.通过令牌获取用户名
		String username = (String)token.getPrincipal();
		//2.判断缓存中是否包含含有此用户名的Element
		Element element = passwordRetryEhcache.get(username);
		//3.如果没有 ,就创建包含该用户名的Element并添加到缓存
		if (element == null){
			element = new Element(username,new AtomicInteger(0));
			passwordRetryEhcache.put(element);
		}
		//4.如果有 就通过该Element获取AutomicInteger
		AtomicInteger atomicInteger = (AtomicInteger)element.getObjectValue();
		//5.如果AutomicInteger的incrementAndGet大于5 就抛异常
		if (atomicInteger.incrementAndGet() > 5){
			throw new ExcessiveAttemptsException();
		}
		//6.如果小于5 就调用父类的doCredentialsMatch方法进行密码验证
		boolean matchs = super.doCredentialsMatch(token, info);
		//7.如果验证通过,就将该username从缓存中清除
		if (matchs){
			passwordRetryEhcache.remove(username);
		}
		return matchs;
	}
}

4.编写自定义的Realm

package com.github.zhangkaitao.shiro.chapter5.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * @author 杨郑耀
 * @description
 * @create 2019-06-13-17:12
 */
public class MyRealm2 extends AuthorizingRealm {
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		return null;
	}

	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		//模拟从数据库获取的用户名及密码
		String username = "liu"; //用户名及salt1
		String salt2 = "0072273a5d87322163795118fdd7c45e"; //盐
		String password = "be320beca57748ab9632c4121ccac0db"; //加密后的密码
		SimpleAuthenticationInfo ai = new SimpleAuthenticationInfo(username, password, getName());
		ai.setCredentialsSalt(ByteSource.Util.bytes(username+salt2)); //盐是用户名+随机数
		return ai;
	}
}

5.配置ini文件

[main]
#自定义credentialsMatcher
credentialsMatcher=com.github.zhangkaitao.shiro.chapter5.credentials.RetryLimitHashedCredentialsMatcher
#算法设置为MD5
credentialsMatcher.hashAlgorithmName=md5
#迭代次数
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true

#自定义realm
myRealm=com.github.zhangkaitao.shiro.chapter5.realm.MyRealm2

#将自定义的credentialsMatcher注入到myRealm
myRealm.credentialsMatcher=$credentialsMatcher

securityManager.realms=$myRealm

6.编写测试类

package com.github.zhangkaitao.shiro.chapter5.test;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.util.ThreadContext;
import org.junit.After;

/**
 * @author 杨郑耀
 * @description
 * @create 2019-06-12-11:16
 */
public abstract class BaseTest {

	@After
	public void tearDown() throws Exception{
		ThreadContext.unbindSubject();//解除Subject绑定到线程
	}

	protected void login(String configFile, String name, String password) throws AuthenticationException {
		//1.获取SecurityManager工厂,通过Ini配置初始化ecurityManager
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
		//2.获取SecurityManager实例,并绑定SecurityUtils
		SecurityManager securityManager = factory.getInstance();
		SecurityUtils.setSecurityManager(securityManager);
		//3.得到Subject及创建用户/密码 身份验证
		Subject subject = SecurityUtils.getSubject();
		UsernamePasswordToken token = new UsernamePasswordToken(name,password);

		subject.login(token);
	}

	//获取Subject
	public Subject subject(){
		return SecurityUtils.getSubject();
	}
}
package com.github.zhangkaitao.shiro.chapter5.test;

import com.sun.javafx.css.converters.EnumConverter;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.converters.AbstractConverter;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.junit.Test;

/**
 * @author 杨郑耀
 * @description
 * @create 2019-06-13-15:57
 */
public class PasswordTest extends BaseTest{
    //利用HashedCredentialsMatcher 防止密码被暴力破解  密码验证失败5次 就禁用
	@Test(expected = ExcessiveAttemptsException.class)
	public void testRetryLimitHashCredentialsMatcherWithMyRealm(){
		//模拟5次验证失败
		for (int i = 1; i <= 5; i++){
			try {
				// 模拟密码错误 所以每次都是IncorrectCredentialsException
				login("classpath:shiro-retryLimitHashedCredentialsMatcher.ini","liu","234");
			} catch (AuthenticationException e) {
				e.printStackTrace();
			}
		}
		//失败5次后再次验证 即使密码正确 也会报错ExcessiveAttemptsException 
		login("classpath:shiro-retryLimitHashedCredentialsMatcher.ini","liu","123");
	}
}

总结:主要思想是,自定义Realm提供原始数据,包括用户名、加密后的密码、盐,然后将这些原始数据添加到认证信息AuthenticationInfo中。自定义CredentialsMatche,实现调用父类doCredentialsMatch方法进行密码验证之前,对密码验证次数进行判定,当次数大于限制数就抛异常。Ehcache提供用户名缓存服务,当该用户名存在于缓存中就将其AtomicInteger加1,不存在就将该用户名添加到缓存中。如果验证通过,将该用户名从缓存清除。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello_中年人

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值