爲了防止被惡意暴力破解,我們都會進行登錄失敗超過一定次數進行鎖定賬號禁止登錄。使用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