Quellcode durchsuchen

添加密码重试,密码错误5次后,锁定10分钟

wangjiang988 vor 2 Jahren
Ursprung
Commit
c78971e2b2

+ 1 - 1
src/main/java/platform/common/Constant.java

@@ -46,7 +46,7 @@ public final class Constant {
     public static final String USER_HAS_LOCK = "账户已锁定!";
     public static final String USER_HAS_REGISTERING = "账号注册中!";
     public static final String USER_REVIEWING = "账户审核中!";
-    public static final String USER_ERROR_MANY = "密码错误次数过多!";
+    public static final String USER_ERROR_MANY = "密码错误次数过多,请10分钟后再试!";
     public static final String USER_PHONE_EMPTY = "用户手机号未完善!";
     public static final String USER_CODE_INVALIAD = "CODE失效!";
 

+ 67 - 0
src/main/java/platform/config/shiro/RetryLimitCredentialsMatcher.java

@@ -0,0 +1,67 @@
+package platform.config.shiro;
+
+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 java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 验证器,增加了登录次数校验功能
+ */
+
+public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
+
+    /**
+     * 密码输入错误次数就被冻结
+     */
+    private Integer errorPasswordTimes=5;
+
+    private Cache<String, AtomicInteger> passwordRetryCache;
+
+    /**
+     * 构造方法 创建对象,传入缓存的管理器
+     * @param cacheManager
+     */
+    public RetryLimitCredentialsMatcher(CacheManager cacheManager) {
+        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
+    }
+
+    /**
+     * 方法名: doCredentialsMatch
+     * 方法描述: 用户登录错误次数方法.
+     * 修改日期: 2019/2/26 20:19
+     * @param token
+     * @param info
+     * @return boolean
+     * @throws
+     */
+    @Override
+    public boolean doCredentialsMatch(AuthenticationToken token,
+                                      AuthenticationInfo info) {
+        String username = (String) token.getPrincipal();
+        Set<String> keys = passwordRetryCache.keys();
+
+        // retry count + 1
+        AtomicInteger retryCount = passwordRetryCache.get(username);
+        if (retryCount == null) {
+            retryCount = new AtomicInteger(0);
+            passwordRetryCache.put(username, retryCount);
+        }
+        if (retryCount.incrementAndGet() > errorPasswordTimes) {
+            // if retry count > 5 throw
+            throw new ExcessiveAttemptsException();
+        }
+
+        boolean matches = super.doCredentialsMatch(token, info);
+        if (matches) {
+            // clear retry count
+            passwordRetryCache.remove(username);
+        }
+        return matches;
+    }
+}

+ 21 - 8
src/main/java/platform/config/shiro/ShiroConfig.java

@@ -158,10 +158,11 @@ public class ShiroConfig {
      * @return
      */
     @Bean(name = "securityManager")
-    public SecurityManager getSecurityManager(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
+    public SecurityManager getSecurityManager(EhCacheManagerFactoryBean ehCacheManagerFactoryBean,
+                                              RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {
         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
         //注入自定义Realm
-        securityManager.setRealm(getAuthenticationRealm());
+        securityManager.setRealm(getAuthenticationRealm(retryLimitCredentialsMatcher));
         //注入缓存管理器
         securityManager.setCacheManager(getCacheShiroManage(ehCacheManagerFactoryBean));
         //注入session管理器
@@ -175,10 +176,11 @@ public class ShiroConfig {
      * @return
      */
     @Bean
-    public AuthenticationRealm getAuthenticationRealm() {
+    public AuthenticationRealm getAuthenticationRealm(RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {
         AuthenticationRealm authenticationRealm = new AuthenticationRealm();
         //将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列
-        authenticationRealm.setCredentialsMatcher(getHashedCredentialsMatcher());
+//        authenticationRealm.setCredentialsMatcher(getHashedCredentialsMatcher());
+        authenticationRealm.setCredentialsMatcher(retryLimitCredentialsMatcher);
 
         //配置缓存
         //开启缓存
@@ -280,6 +282,15 @@ public class ShiroConfig {
         return ehCacheManager;
     }
 
+    @Bean
+    public RetryLimitCredentialsMatcher getRetryLimit(CacheManager cacheManager){
+        RetryLimitCredentialsMatcher retryLimitCredentialsMatcher = new RetryLimitCredentialsMatcher(cacheManager);
+        retryLimitCredentialsMatcher.setHashAlgorithmName(ShiroKit.HASH_ALGORITHM_NAME);
+        retryLimitCredentialsMatcher.setHashIterations(ShiroKit.HASHITERATIONS);
+        retryLimitCredentialsMatcher.setStoredCredentialsHexEncoded(true);
+        return retryLimitCredentialsMatcher;
+    }
+
     /**
      * 会话Cookie模板
      *
@@ -311,10 +322,11 @@ public class ShiroConfig {
      * @return
      */
     @Bean
-    public MethodInvokingFactoryBean getMethodInvokingFactoryBean(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
+    public MethodInvokingFactoryBean getMethodInvokingFactoryBean(EhCacheManagerFactoryBean ehCacheManagerFactoryBean,
+                                                                  RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {
         MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
         factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
-        factoryBean.setArguments(new Object[]{getSecurityManager(ehCacheManagerFactoryBean)});
+        factoryBean.setArguments(new Object[]{getSecurityManager(ehCacheManagerFactoryBean, retryLimitCredentialsMatcher)});
         return factoryBean;
     }
 
@@ -333,9 +345,10 @@ public class ShiroConfig {
     }
 
     @Bean
-    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
+    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(EhCacheManagerFactoryBean ehCacheManagerFactoryBean,
+                     RetryLimitCredentialsMatcher retryLimitCredentialsMatcher) {
         AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
-        advisor.setSecurityManager(getSecurityManager(ehCacheManagerFactoryBean));
+        advisor.setSecurityManager(getSecurityManager(ehCacheManagerFactoryBean, retryLimitCredentialsMatcher));
         return advisor;
     }
 

+ 230 - 0
src/main/java/platform/config/shiro/ShiroKit.java

@@ -0,0 +1,230 @@
+package platform.config.shiro;
+
+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 = ",";
+
+    /**
+     * 加盐参数
+     */
+    public final static String HASH_ALGORITHM_NAME = "MD5";
+
+    /**
+     * 循环次数
+     */
+    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();
+    }
+
+
+    /**
+     * 获取当前 Subject.
+     * Subject表示单个应用程序用户的状态和安全操作。
+     * 这些操作包括身份验证(登录/注销),授权(访问控制)和会话访问。
+     * 这是Shiro的单用户安全功能的主要机制。
+     *
+     * @return Subject
+     */
+    public static Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+
+    /**
+     * 从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;
+    }
+
+    /**
+     * 设置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);
+    }
+
+    /**
+     * 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
+     *
+     * @param roleName 角色名
+     * @return 属于该角色:true,否则false
+     */
+    public static boolean hasRole(String roleName) {
+        return getSubject() != null && roleName != null
+                && roleName.length() > 0 && getSubject().hasRole(roleName);
+    }
+
+    /**
+     * 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
+     *
+     * @param roleName 角色名
+     * @return 不属于该角色:true,否则false
+     */
+    public static boolean lacksRole(String roleName) {
+        return !hasRole(roleName);
+    }
+
+    /**
+     * 验证当前用户是否属于以下任意一个角色。
+     *
+     * @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;
+    }
+
+    /**
+     * 验证当前用户是否属于以下所有角色。
+     *
+     * @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;
+    }
+
+    /**
+     * 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
+     *
+     * @param permission 权限名
+     * @return 拥有权限:true,否则false
+     */
+    public static boolean hasPermission(String permission) {
+        return getSubject() != null && permission != null
+                && permission.length() > 0
+                && getSubject().isPermitted(permission);
+    }
+
+    /**
+     * 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
+     *
+     * @param permission 权限名
+     * @return 拥有权限:true,否则false
+     */
+    public static boolean lacksPermission(String permission) {
+        return !hasPermission(permission);
+    }
+
+    /**
+     * 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用
+     *
+     * @return 通过身份验证:true,否则false
+     */
+    public static boolean isAuthenticated() {
+        return getSubject() != null && getSubject().isAuthenticated();
+    }
+
+    /**
+     * 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。
+     *
+     * @return 没有通过身份验证:true,否则false
+     */
+    public static boolean notAuthenticated() {
+        return !isAuthenticated();
+    }
+
+    /**
+     * 认证通过或已记住的用户。与guset搭配使用。
+     *
+     * @return 用户:true,否则 false
+     */
+    public static boolean isUser() {
+        return getSubject() != null && getSubject().getPrincipal() != null;
+    }
+
+    /**
+     * 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用
+     *
+     * @return 访客:true,否则false
+     */
+    public static boolean isGuest() {
+        return !isUser();
+    }
+
+    /**
+     * 输出当前用户信息,通常为登录帐号信息。
+     *
+     * @return 当前用户信息
+     */
+    public static String principal() {
+        if (getSubject() != null) {
+            Object principal = getSubject().getPrincipal();
+            return principal.toString();
+        }
+        return "";
+    }
+
+}

+ 4 - 0
src/main/java/platform/modules/home/web/HomeRefactorController.java

@@ -671,6 +671,9 @@ public class HomeRefactorController extends BaseController {
             //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
             //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
             log.info("对用户进行登录验证..验证开始! username = {}", username);
+//            if (checkUserLock(username)) {
+//                return ResponseMessage.error(Constant.USER_HAS_LOCK);
+//            }
             currentUser.login(token);
             //验证是否登录成功
             if (currentUser.isAuthenticated()) {
@@ -687,6 +690,7 @@ public class HomeRefactorController extends BaseController {
             }
         } catch (UnknownAccountException e) {  //账号不存在
             log.info("! username = {}", username);
+//            recordLoginFail(username);
             return ResponseMessage.error(Constant.USER_NOT_FIND);
 
         } catch (IncorrectCredentialsException e) {

+ 10 - 0
src/main/resources/ehcache-shiro.xml

@@ -52,6 +52,16 @@
            diskPersistent="true"
            diskExpiryThreadIntervalSeconds="600"/>
 
+    <!-- 登录记录缓存 锁定10分钟 -->
+    <cache name="passwordRetryCache"
+           eternal="false"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="0"
+           overflowToDisk="false"
+           statistics="true"
+           maxEntriesLocalHeap="0">
+    </cache>
+
     <!--活动session缓存-->
     <cache name="shiro-activeSessionCache"
            maxElementsInMemory="10000"