Shiro安全框架学习04 - 登录失败次数限制

为了防止被恶意暴力破解,我们都会进行登录失败超过一定次数进行锁定账号禁止登录。使用Ehcache提供缓存服务。

在前几篇代码的基础上添加ehcache依赖
<dependency>
	<groupId>net.sf.ehcache</groupId>
	<artifactId>ehcache-core</artifactId>
	<version>2.6.6</version>
</dependency>
配置ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

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

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

</ehcache>
编写自定义的CredentialsMatcher
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 org.apache.shiro.cache.Cache;
import java.util.concurrent.atomic.AtomicInteger;

public class RetryLimitHashedCredentialsMatcher extends
		HashedCredentialsMatcher {

	private Ehcache passwordRetryCache;

    public RetryLimitHashedCredentialsMatcher() {
        CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }

	@Override
	public boolean doCredentialsMatch(AuthenticationToken token,
			AuthenticationInfo info) {
		String username = (String)token.getPrincipal();
        //retry count + 1
        Element element = passwordRetryCache.get(username);
        if(element == null) {
            element = new Element(username , new AtomicInteger(0));
            passwordRetryCache.put(element);
        }
        AtomicInteger retryCount = (AtomicInteger)element.getObjectValue();
        if(retryCount.incrementAndGet() > 5) {
            //if retry count > 5 throw
            throw new ExcessiveAttemptsException();
        }

        boolean matches = super.doCredentialsMatch(token, info);
        if(matches) {
            //clear retry count
            passwordRetryCache.remove(username);
        }
        return matches;
	}
}
编写自定义的Realm
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;


public class UserRealm extends AuthorizingRealm  {

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		// TODO Auto-generated method stub
		
		//获取账号密码
		UsernamePasswordToken t = (UsernamePasswordToken) token;
        String password = new String(t.getPassword());
		String username = (String) token.getPrincipal();
		
		//获取数据库种的密码、盐(此处写死,测试用)
		User user = new User();
		user.setUsername(username);
		user.setPassword("b4b88b2ef28089f9c802df11bd216a33");
		user.setSalt("713183f112f48824613f0f05d0d10060");
		
		if (!"itmyhome".equals(username)) {
			// 抛出 帐号找不到异常
			throw new UnknownAccountException();
		}
		
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
				user.getUsername(), // 用户名
				user.getPassword(), // 密码
				ByteSource.Util.bytes(password + user.getSalt()),// salt=password+salt
				getName() // realm name
		);
		return authenticationInfo;
	}

}
测试类
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ShiroTest {

	private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

	public static void main(String[] args) {
      
		//获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager  
        Factory<SecurityManager> factory =  new IniSecurityManagerFactory("classpath:auth.ini");
 
        //得到SecurityManager实例 并绑定给SecurityUtils  
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        
        //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)  
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("itmyhome","1234561");
        
        //模拟多次登录,密码错误超过5次即锁定账户
    	for (int i = 1; i <= 6; i++){ 
	        try{
	        	//登录,即身份验证  
	        	subject.login(token);
	        }catch(ExcessiveAttemptsException e){
	        	System.out.println("###### 账户已锁定 ########");
	        }catch(IncorrectCredentialsException e){
	        	System.out.println("###### 密码错误 ########");
	        }catch(AuthenticationException e){
	        	//身份验证失败
	        	System.out.println("###### 账号错误 ########");
	        }
    	}
        System.out.println("User is authenticated:  " + subject.isAuthenticated());
    }
}

示例源码地址:xxx

发布了691 篇原创文章 · 获赞 2948 · 访问量 409万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章