日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot+Shiro+ehcache实现登录失败超次数锁定帐号

發(fā)布時間:2024/9/27 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot+Shiro+ehcache实现登录失败超次数锁定帐号 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

            • 二、Controller層接收登錄請求
            • 三、自定義的Realm
            • 四、密碼驗證器增加登錄次數(shù)校驗功能
            • 五、ShiroConfig的配置類
            • 六、EhCache 的配置
            • 七、全局異常的配置

####### 一、 Shiro的執(zhí)行流程

1、核心介紹
1)Application Code用戶編寫代碼
2)Subject就是shiro管理的用戶
3)SecurityManager安全管理器,就是shiro權(quán)限控制核心對象,在編程時,只需要操作Subject方法,底層調(diào)用SecurityManager方法,無需直接編程操作SecurityManager
4)Realm應(yīng)用程序和安全數(shù)據(jù)之間連接器,應(yīng)用程序進(jìn)行權(quán)限控制讀取安全數(shù)據(jù)(數(shù)據(jù)表、文件、網(wǎng)絡(luò)…)通過Realm對象完成
2、Shiro執(zhí)行流程
應(yīng)用程序(就是你自己的項目)—>Subject—>SecurityManager—>Realm—>安全數(shù)據(jù)
3、Shiro進(jìn)行權(quán)限控制的四種主要方式
1)在程序中通過Subject編程方式進(jìn)行權(quán)限控制
2)配置Filter實現(xiàn)URL級別粗粒度權(quán)限控制
3)配置代理,基于注解實現(xiàn)細(xì)粒度權(quán)限控制
4)在頁面中使用shiro自定義標(biāo)簽實現(xiàn),頁面顯示權(quán)限控制

Shiro執(zhí)行登錄的流程如下圖.

大致的思路如下, 在Controller層接收前端輸入的用戶名和密碼. 調(diào)用Shiro的SecurityUtils.getSubject()方法獲取Subject對象.
之后用Subject對象調(diào)用login方法,其Shiro底層會進(jìn)行密碼的驗證, 傳入UsernamePasswordToken對象,此對象封裝了前端傳入的用戶名和密碼.

接著Shiro的SecurityManager會去調(diào)用自定義的Realm的AuthenticationInfo方法進(jìn)行登錄的驗證, 此方法會返回一個SimpleAuthenticationInfo對象,此對象封裝了ShiroUser ,數(shù)據(jù)庫中存儲的當(dāng)前用戶的密碼, 密碼加鹽的值,Realm的名稱, 即把數(shù)據(jù)庫中的當(dāng)前的用戶, 與用戶輸入的用戶名密碼即存儲在 UsernamePasswordToken進(jìn)行比較,如果密碼正確,登錄成功,密碼不正確登錄失敗.


調(diào)用自定義Realm的AuthenticationInfo完了之后, 調(diào)用RetryLimitCredentialsMatcher類中的doCredentialsMatch方法, 進(jìn)行密碼匹配次數(shù)的記錄. 并用EhCache作為緩存, 把當(dāng)前登錄的用戶名作為key,key的過期時間按照需求設(shè)置即可, 把登錄的次數(shù)作為值.首先通過用戶名,獲取登錄次數(shù),如果登錄次數(shù)為0, 那么先給當(dāng)前用戶設(shè)置一個緩存,登錄次數(shù)+1,之后判斷是否大于限定的登錄錯誤次數(shù),如果超過了限定次數(shù),則拋出異常,用全局的異常攔截器,攔截此異常, 記錄登錄錯誤次數(shù)的異常, 并封裝登錄次數(shù)過多的提示,給客戶端. 具體的代碼在下面.

二、Controller層接收登錄請求

Shiro的工具類ShiroKit

import org.apache.shiro.SecurityUtils; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ByteSource;import java.util.List;/*** shiro工具類***/ public class ShiroKit {/*** 名稱分隔符*/private static final String NAMES_DELIMETER = ",";/*** 加鹽參數(shù)*/public final static String HASH_ALGORITHM_NAME = "MD5";/*** 循環(huán)次數(shù)*/public final static int HASHITERATIONS = 1024;/*** 驗證是否同一個賬號重新登錄的屬性,為true代表是重新登錄, 初始化為false,代表不是重新登錄*/public static boolean ISPEATEDLOGIN =false;/*** shiro密碼加密工具類** @param credentials 密碼* @param saltSource 密碼鹽* @return*/public static String md5(String credentials, String saltSource) {ByteSource salt = new Md5Hash(saltSource);return new SimpleHash(HASH_ALGORITHM_NAME, credentials, salt, HASHITERATIONS).toString();}/*** 獲取隨機鹽值** @param length* @return*/public static String getRandomSalt(int length) {return ToolUtil.getRandomString(length);}/*** 獲取當(dāng)前 Subject.* Subject表示單個應(yīng)用程序用戶的狀態(tài)和安全操作。* 這些操作包括身份驗證(登錄/注銷),授權(quán)(訪問控制)和會話訪問。* 這是Shiro的單用戶安全功能的主要機制。** @return Subject*/public static Subject getSubject() {return SecurityUtils.getSubject();}/*** 獲取封裝的 ShiroUser** @return ShiroUser*/public static ShiroUser getUser() {if (isGuest()) {return null;} else {return (ShiroUser) getSubject().getPrincipals().getPrimaryPrincipal();}}/*** 從shiro獲取session*/public static Session getSession() {return getSubject().getSession();}/*** 獲取shiro指定的sessionKey*/@SuppressWarnings("unchecked")public static <T> T getSessionAttr(String key) {Session session = getSession();return session != null ? (T) session.getAttribute(key) : null;}/*** 設(shè)置shiro指定的sessionKey*/public static void setSessionAttr(String key, Object value) {Session session = getSession();session.setAttribute(key, value);}/*** 移除shiro指定的sessionKey*/public static void removeSessionAttr(String key) {Session session = getSession();if (session != null)session.removeAttribute(key);}/*** 驗證當(dāng)前用戶是否屬于該角色?,使用時與lacksRole 搭配使用** @param roleName 角色名* @return 屬于該角色:true,否則false*/public static boolean hasRole(String roleName) {return getSubject() != null && roleName != null&& roleName.length() > 0 && getSubject().hasRole(roleName);}/*** 與hasRole標(biāo)簽邏輯相反,當(dāng)用戶不屬于該角色時驗證通過。** @param roleName 角色名* @return 不屬于該角色:true,否則false*/public static boolean lacksRole(String roleName) {return !hasRole(roleName);}/*** 驗證當(dāng)前用戶是否屬于以下任意一個角色。** @param roleNames 角色列表* @return 屬于:true,否則false*/public static boolean hasAnyRoles(String roleNames) {boolean hasAnyRole = false;Subject subject = getSubject();if (subject != null && roleNames != null && roleNames.length() > 0) {for (String role : roleNames.split(NAMES_DELIMETER)) {if (subject.hasRole(role.trim())) {hasAnyRole = true;break;}}}return hasAnyRole;}/*** 驗證當(dāng)前用戶是否屬于以下所有角色。** @param roleNames 角色列表* @return 屬于:true,否則false*/public static boolean hasAllRoles(String roleNames) {boolean hasAllRole = true;Subject subject = getSubject();if (subject != null && roleNames != null && roleNames.length() > 0) {for (String role : roleNames.split(NAMES_DELIMETER)) {if (!subject.hasRole(role.trim())) {hasAllRole = false;break;}}}return hasAllRole;}/*** 驗證當(dāng)前用戶是否擁有指定權(quán)限,使用時與lacksPermission 搭配使用** @param permission 權(quán)限名* @return 擁有權(quán)限:true,否則false*/public static boolean hasPermission(String permission) {return getSubject() != null && permission != null&& permission.length() > 0&& getSubject().isPermitted(permission);}/*** 與hasPermission標(biāo)簽邏輯相反,當(dāng)前用戶沒有制定權(quán)限時,驗證通過。** @param permission 權(quán)限名* @return 擁有權(quán)限:true,否則false*/public static boolean lacksPermission(String permission) {return !hasPermission(permission);}/*** 已認(rèn)證通過的用戶。不包含已記住的用戶,這是與user標(biāo)簽的區(qū)別所在。與notAuthenticated搭配使用** @return 通過身份驗證:true,否則false*/public static boolean isAuthenticated() {return getSubject() != null && getSubject().isAuthenticated();}/*** 未認(rèn)證通過用戶,與authenticated標(biāo)簽相對應(yīng)。與guest標(biāo)簽的區(qū)別是,該標(biāo)簽包含已記住用戶。。** @return 沒有通過身份驗證:true,否則false*/public static boolean notAuthenticated() {return !isAuthenticated();}/*** 認(rèn)證通過或已記住的用戶。與guset搭配使用。** @return 用戶:true,否則 false*/public static boolean isUser() {return getSubject() != null && getSubject().getPrincipal() != null;}/*** 驗證當(dāng)前用戶是否為“訪客”,即未認(rèn)證(包含未記住)的用戶。用user搭配使用** @return 訪客:true,否則false*/public static boolean isGuest() {return !isUser();}/*** 輸出當(dāng)前用戶信息,通常為登錄帳號信息。** @return 當(dāng)前用戶信息*/public static String principal() {if (getSubject() != null) {Object principal = getSubject().getPrincipal();return principal.toString();}return "";}}
三、自定義的Realm
import cn.stylefeng.roses.core.util.HttpContext; import cn.stylefeng.roses.core.util.ToolUtil; import cn.utry.govaffairs.core.shiro.service.UserAuthService; import cn.utry.govaffairs.core.shiro.service.impl.UserAuthServiceServiceImpl; import cn.utry.govaffairs.modular.system.model.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 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.session.mgt.eis.SessionDAO; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set;public class ShiroDbRealm extends AuthorizingRealm {@Autowiredprivate SessionDAO sessionDAO;/*** 登錄認(rèn)證 證明 鑒定*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)throws AuthenticationException {// 獲取shirorealm所需數(shù)據(jù)的Service層UserAuthService shiroFactory = UserAuthServiceServiceImpl.me();//獲取Controller層傳遞的token,包含了前端輸入的用戶名和密碼 一個簡單的用戶名/密碼身份驗證令牌UsernamePasswordToken token = (UsernamePasswordToken) authcToken;//獲取登錄的用戶名String username = token.getUsername();//根據(jù)前端輸入的賬號, 去數(shù)據(jù)庫查詢用戶信息. 此時如果賬號不存在(包括邏輯刪除)或被凍結(jié),直接拋出異常,終止登錄User user = shiroFactory.user(token.getUsername());//進(jìn)行用戶的驗證ShiroUser shiroUser = shiroFactory.shiroUser(user);// 獲取需要登錄的用戶在數(shù)據(jù)庫中存儲的加鹽的密碼String credentials = user.getPassword();// 獲取需要登錄的用戶在數(shù)據(jù)庫中存儲的密碼的鹽值String source = user.getSalt();ByteSource credentialsSalt = new Md5Hash(source);// 創(chuàng)建SimpleAuthenticationInfo 返回給shiro的安全管理器去比較當(dāng)前登錄用戶輸入的密碼,與數(shù)據(jù)庫中存儲的加鹽的密碼是否一致// 即在登錄的Controller層 UsernamePasswordToken 中存儲了當(dāng)前輸入的用戶名與密碼, 與此SimpleAuthenticationInfo 進(jìn)行比較// 如果密碼一致,代表登錄成功, 密碼不一致,則報密碼錯誤的異常return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);}/*** 權(quán)限認(rèn)證 授權(quán),認(rèn)可*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}
四、密碼驗證器增加登錄次數(shù)校驗功能
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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;import java.util.Set; import java.util.concurrent.atomic.AtomicInteger;/*** 驗證器,增加了登錄次數(shù)校驗功能*/public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {/*** 密碼輸入錯誤次數(shù)就被凍結(jié)*/private Integer errorPasswordTimes=5;private Cache<String, AtomicInteger> passwordRetryCache;/*** 構(gòu)造方法 創(chuàng)建對象,傳入緩存的管理器* @param cacheManager*/public RetryLimitCredentialsMatcher(CacheManager cacheManager) {passwordRetryCache = cacheManager.getCache("passwordRetryCache");}/*** 方法名: doCredentialsMatch* 方法描述: 用戶登錄錯誤次數(shù)方法.* 修改日期: 2019/2/26 20:19* @param token* @param info* @return boolean* @throws*/@Overridepublic boolean doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info) {String username = (String) token.getPrincipal();Set<String> keys = passwordRetryCache.keys();// retry count + 1AtomicInteger retryCount = passwordRetryCache.get(username);if (retryCount == null) {retryCount = new AtomicInteger(0);passwordRetryCache.put(username, retryCount);}if (retryCount.incrementAndGet() > errorPasswordTimes) {// if retry count > 5 throwthrow new ExcessiveAttemptsException();}boolean matches = super.doCredentialsMatch(token, info);if (matches) {// clear retry countpasswordRetryCache.remove(username);}return matches;} }
五、ShiroConfig的配置類

在此配置類中, 要注意的是把ShiroDbRealm的bean中要調(diào)用set方法注入retryLimitCredentialsMatcher,否則密碼錯誤次數(shù)的校驗不會生效.

@Configuration public class ShiroConfig {/*** Shiro生命周期處理器:* 用于在實現(xiàn)了Initializable接口的Shiro bean初始化時調(diào)用Initializable接口回調(diào)(例如:UserRealm)* 在實現(xiàn)了Destroyable接口的Shiro bean銷毀時調(diào)用 Destroyable接口回調(diào)(例如:DefaultSecurityManager)*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 方法名: getDefaultAdvisorAutoProxyCreator* 方法描述: 開啟Shiro的注解模式* 修改日期: 2019/2/25 16:03* @param* @return org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator* @author taohongchao* @throws*/@Beanpublic DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();autoProxyCreator.setProxyTargetClass(true);return autoProxyCreator;}/*** 安全管理器*/@Beanpublic DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager,CacheManager cacheShiroManager,SessionManager sessionManager,RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//把自定義的Realm注入安全管理器中securityManager.setRealm(this.shiroDbRealm(retryLimitCredentialsMatcher));securityManager.setCacheManager(cacheShiroManager);securityManager.setRememberMeManager(rememberMeManager);securityManager.setSessionManager(sessionManager);return securityManager;}/*** 緩存管理器 使用Ehcache實現(xiàn)*/@Beanpublic CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {EhCacheManager ehCacheManager = new EhCacheManager();ehCacheManager.setCacheManager(ehcache.getObject());ehCacheManager.setCacheManagerConfigFile("ehcache.xml");return ehCacheManager;}@Beanpublic RetryLimitCredentialsMatcher getRetryLimit(CacheManager cacheManager){RetryLimitCredentialsMatcher retryLimitCredentialsMatcher = new RetryLimitCredentialsMatcher(cacheManager);retryLimitCredentialsMatcher.setHashAlgorithmName(ShiroKit.HASH_ALGORITHM_NAME);retryLimitCredentialsMatcher.setHashIterations(ShiroKit.HASHITERATIONS);retryLimitCredentialsMatcher.setStoredCredentialsHexEncoded(true);return retryLimitCredentialsMatcher;}/*** 項目自定義的Realm*/@Beanpublic ShiroDbRealm shiroDbRealm(RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {ShiroDbRealm shiroDbRealm = new ShiroDbRealm();shiroDbRealm.setCredentialsMatcher(retryLimitCredentialsMatcher);return shiroDbRealm;} }
六、EhCache 的配置

EhCache.xml中的配置, 其中設(shè)置了名稱為passwordRetryCache的緩存,用于凍結(jié)密碼輸入錯誤次數(shù)多過的緩存.

<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="ehcache.xsd"updateCheck="false" monitoring="autodetect"dynamicConfig="true" ><diskStore path="java.io.tmpdir/ehcache"/><defaultCachemaxElementsInMemory="50000"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="3600"overflowToDisk="true"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"/><!-- 登錄記錄緩存 鎖定10分鐘 --><cache name="passwordRetryCache"eternal="false"timeToIdleSeconds="600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"maxEntriesLocalHeap="0"></cache></ehcache>

EhCacheConfig的配置類

import net.sf.ehcache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource;/*** ehcache配置** @author* @date 2017-05-20 23:11*/ @Configuration @EnableCaching public class EhCacheConfig {/*** EhCache的配置*/@Beanpublic EhCacheCacheManager cacheManager(CacheManager cacheManager) {return new EhCacheCacheManager(cacheManager);}/*** EhCache的配置*/@Beanpublic EhCacheManagerFactoryBean ehcache() {EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));return ehCacheManagerFactoryBean;} }
七、全局異常的配置

當(dāng)密碼輸入錯誤次數(shù)過多時,拋出ExcessiveAttemptsException異常,被此異常攔截器攔截

@ControllerAdvice @Order(-1) public class GlobalExceptionHandler {/*** 方法名: excessiveAttemptsException* 方法描述: 登錄錯誤次數(shù)過多異常 * @throws*/@ExceptionHandler(ExcessiveAttemptsException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)public String excessiveAttemptsException(ExcessiveAttemptsException e, Model model) {String username = getRequest().getParameter("username");LogManager.me().executeLog(LogTaskFactory.loginLog(username, "登錄錯誤次數(shù)超過五次", getIp()));model.addAttribute("tips", "登錄錯誤次數(shù)超過五次,請十分鐘后登錄!");return "/login.html";} }

最終的效果如圖所示

總結(jié)

以上是生活随笔為你收集整理的SpringBoot+Shiro+ehcache实现登录失败超次数锁定帐号的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。