shiro笔记2

这是一篇笔记,会随时跟着shiro的学习更新

关于Shrio自带的Exception使用

在用户登录的时候会有这样的判断

 String username = users.getUserName();
        logger.info("用户paswd为:"+users.getPassword());
        UsernamePasswordToken token = new UsernamePasswordToken(username, users.getPassword(),true);
        Subject currentUser = SecurityUtils.getSubject();
        try {
            logger.info("对用户[" + username + "]进行登录验证..验证开始");
            currentUser.login(token);
            logger.info("对用户[" + username + "]进行登录验证..验证通过");
        }catch(UnknownAccountException uae){
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户or state=0");
            model.addAttribute("error", "无此账户/未激活");
        }catch(IncorrectCredentialsException ice){
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
            model.addAttribute("error", "密码不正确");
        }catch(LockedAccountException lae){
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
            model.addAttribute("error", "账户已锁定");
        }catch(ExcessiveAttemptsException eae){
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
            model.addAttribute("error", "用户名或密码错误次数过多");
        }catch(AuthenticationException ae){
            logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
            model.addAttribute("error", "用户名或密码不正确");
            ae.printStackTrace();
        }

这里有很多exception,其中每一个logger.info的描述很清楚。
但是如何使用呢。
我们日常验证的时候都是在ShrioRealm丢参来判定登录和权限。
假设,我们目前user表中。有一个Status来代表状态。1代表不被锁定账号。其他都是。
那么可以这么用

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken)
            throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        logger.info("验证当前Subject时获取到token为:" + token.toString());
        User user = userService.selectAllByName(token.getUsername());
        if (user != null) {
            Object credentials = user.getPassword();
            ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUserName());
      //判定管理员有没有锁
         if(userMapper.selectStatus(user.getUserName())!=1){
                throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ");
            }
            return new SimpleAuthenticationInfo(user, credentials, credentialsSalt, getName());
        }
        return null;
    }

在这里,我添加了

if(userMapper.selectStatus(user.getUserName())!=1){
                throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ");
            }

这一个判断语句。
从userMapper映射中获取对于UserName的那一列来得出status的值。

所以。我们在AuthenticationInfo预先处理这个status字段。然后通过

throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ")

来得出让我们的从前端输入的username值,直接被锁。

输入次数过多,锁几分钟

根据这个标题,我们可以对照以下我最早放在上面的代码得出是那个Exception。
也就是

ExcessiveAttemptsException

那么怎么使用呢。
分析一下。在shiro配置文件添加RetryLimitHashedCredentialsMatcher。然后实现RetryLimitHashedCredentialsMatcher。最后在判定登录的请求中判断输入的次数,然后添加对它的引用。其中。我们需要用到ehcache来存入错误的次数和等待的时间

第一步

在ShiroConfiguration.java中添加

  @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {

        HashedCredentialsMatcher credentialsMatcher = new  RetryLimitHashedCredentialsMatcher(ehCacheManager());
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1024);
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

重点是第一行。

然后需要在需要在realm配置器中添加

  @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

然后ehcache

 @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {

        HashedCredentialsMatcher credentialsMatcher = new  RetryLimitHashedCredentialsMatcher(ehCacheManager());
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1024);
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

 public EhCacheManager ehCacheManager() {
        logger.debug(
                "=====shiro整合ehcache缓存:ShiroConfiguration.getEhCacheManager()");
        EhCacheManager ehcache = new EhCacheManager();
        CacheManager cacheManager = CacheManager.getCacheManager("shiro");
        if(cacheManager == null){
            try {

                cacheManager = CacheManager.create(ResourceUtils.getInputStreamForPath("classpath:config/ehcache.xml"));

            } catch (CacheException | IOException e) {
                e.printStackTrace();
            }
        }
        ehcache.setCacheManager(cacheManager);
        return ehcache;
    }


  @Bean public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(shiroRealm());
        // //注入ehcache缓存管理器;
        securityManager.setCacheManager(ehCacheManager());
        // //注入session管理器;
        securityManager.setSessionManager(sessionManager());
        //注入Cookie记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

 @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //sessionManager.setCacheManager(ehCacheManager());
        sessionManager.setSessionDAO(enterCacheSessionDAO());
        sessionManager.setSessionIdCookie(sessionIdCookie());
        return sessionManager;
    }

    @Bean
    public EnterpriseCacheSessionDAO enterCacheSessionDAO() {
        EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //添加缓存管理器
        //enterCacheSessionDAO.setCacheManager(ehCacheManager());
        //添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)
        enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return enterCacheSessionDAO;
    }

第二步

创建RetryLimitHashedCredentialsMatcher.java

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    //集群中可能会导致出现验证多过5次的现象,因为AtomicInteger只能保证单节点并发
    //解决方案,利用ehcache、redis(记录错误次数)和mysql数据库(锁定)的方式处理:密码输错次数限制; 或两者结合使用
    private Cache<String, AtomicInteger> passwordRetryCache;
  
    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        //读取ehcache中配置的登录限制锁定时间
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");  
    }

    /**
     * 在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)中进行身份认证的密码匹配,
     * </br>这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,
     * </br>从而实现了如果登录次数超出指定的值就锁定。
     * @param token
     * @param info
     * @return
     */
    @Override  
    public boolean doCredentialsMatch(AuthenticationToken token,
                                      AuthenticationInfo info) {
        //获取登录用户名
        String username = (String) token.getPrincipal();  
        //从ehcache中获取密码输错次数
        // retryCount

        //判定管理员有没有锁

        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            //第一次
            retryCount = new AtomicInteger(0);  
            passwordRetryCache.put(username, retryCount);  
        }
        //retryCount.incrementAndGet()自增:count + 1
        if (retryCount.incrementAndGet() > 5) {  
            // if retry count > 5 throw  超过5次 锁定
            throw new ExcessiveAttemptsException("账号:"+username+" 密码超过5次了,请等待6分钟");
        }

        //否则走判断密码逻辑
        boolean matches = super.doCredentialsMatch(token, info);  
        if (matches) {  
            // clear retry count  清楚ehcache中的count次数缓存
            passwordRetryCache.remove(username);  
        }  
        return matches;  
    }  
} 
第三步

在login判断中写入

接最上面的try catch

 Cache<String, AtomicInteger> passwordRetryCache = ecm
                .getCache("passwordRetryCache");
        if (null != passwordRetryCache) {
            int retryNum = (passwordRetryCache.get(users.getUserName()) == null ? 0
                    : passwordRetryCache.get(users.getUserName())).intValue();
            logger.debug("输错次数:" + retryNum);
            if (retryNum > 0 && retryNum < 6) {
                model.addAttribute("error", "用户名或密码错误" + retryNum + "次,再输错"
                        + (6 - retryNum) + "次账号将锁定");
            }
        }
        //验证是否登录成功
        if(currentUser.isAuthenticated()){
            logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
            LoginLog loginLog = new LoginLog();
            loginLog.setUsername(username);
            loginLog.setSystemBrowserInfo();
            loginLogService.saveLoginLog(loginLog);

	......省略
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值