专栏首页有脑子的搬砖工用户尝试登陆错误次数

用户尝试登陆错误次数

1.引入依赖

本文主要引入的jar包如下:

<dependency>
	    <groupId>org.apache.shiro</groupId>
	    <artifactId>shiro-spring</artifactId>
	    <version>1.4.0</version>
	</dependency>
	<dependency>
	    <groupId>org.apache.shiro</groupId>
	    <artifactId>shiro-ehcache</artifactId>
	    <version>1.4.0</version>
	</dependency>

由于我们将使用shiro + ehache配合使用,所以可以不用单独再引用ehcache.jar了,使用shiro-ehcache时,会自动添加ehcache-core 2.6.11。

2.创建配置文件

resources config ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
	<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
	    updateCheck="false"
	    dynamicConfig="true"
	    name="ehcache_retry">
	 
	    <!-- 磁盘缓存位置 -->
	    <diskStore path="java.io.tmpdir/ehcache" />
	 
	    <!-- 默认缓存 -->
	    <defaultCache maxEntriesLocalHeap="10000" 
	        eternal="false"
	        timeToIdleSeconds="120" 
	        timeToLiveSeconds="120" 
	        maxEntriesLocalDisk="10000000"
	        diskExpiryThreadIntervalSeconds="120" 
	        memoryStoreEvictionPolicy="LRU">
	        <persistence strategy="localTempSwap" />
	    </defaultCache>
	 
	    <!-- helloworld缓存 -->
	    <cache name="passwordRetryCache" 
	        maxElementsInMemory="2000" 
	        eternal="false"
	        timeToIdleSeconds="600" 
	        timeToLiveSeconds="600" 
	        overflowToDisk="false"
	        statistics="true" />

		<!--  
           设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
           缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
           如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
           Hibernate 在不同的缓存区域保存不同的类/集合。
            对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
            对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders
       -->
       <!--  
           name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字 

           maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目 
         
           eternal: 设置对象是否为永久的, true表示永不过期, 此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false 

          timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。 

           timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值 
        
           overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 
       -->

	</ehcache>

关于以上属性代表的含义,可以在官方文档中找到官方文档 配置中不能对ehcache标签添加monitoring=“autodetect”,否侧缓存将无法保存。

3. MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;

    //给当前realm起个名
    @Override
    public String getName() {
        return "customReam02";
    }
    //支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户主身份---用户名
        String username = (String) principalCollection.getPrimaryPrincipal();
        //通过用户名查找用户对应的权限列表
        List<SysPermission> permissionList = sysUserService.findPermission(username);
        //创建一个授权对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        for(SysPermission sysPermission:permissionList){
            authorizationInfo.addStringPermission(sysPermission.getPercode());
        }
        return authorizationInfo;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取身份
        String username = (String) authenticationToken.getPrincipal();
        //通过用户名,查找对应的用户是否存在,如果存在返回用户对象
        SysUser sysUser = sysUserService.findUser(username);
        if(sysUser == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                sysUser.getUsercode(), //用户名
                sysUser.getPassword(), //密码
                ByteSource.Util.bytes(sysUser.getSalt()),//salt
                getName()  //realm name
        );
        return authenticationInfo;
    }
}

4.RetryLimitCredentialsMatcher.java

public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher{
	    private static final int MAX_LOGIN_RETRY_TIMES = 5;
	    private Cache<String, AtomicInteger> passwordRetryCache;
		
	    public RetryLimitCredentialsMatcher(EhCacheManager ehCacheManager) {
	        passwordRetryCache = ehCacheManager.getCache("passwordRetryCache");
	    }
		
	    @Override
	    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws ExcessiveAttemptsException{
	        String userName = (String) token.getPrincipal();
	        AtomicInteger retryCount = passwordRetryCache.get(userName);
	        if (retryCount == null) {
	            // 高并发下使用的线程安全的int类
	            retryCount = new AtomicInteger(0);
	            passwordRetryCache.put(userName, retryCount);
	        }
	        if (retryCount.incrementAndGet() > MAX_LOGIN_RETRY_TIMES) {
	            throw new ExcessiveAttemptsException();
	        }
		
	        boolean match = super.doCredentialsMatch(token, info);
	        if (match) {
	            passwordRetryCache.remove(userName);
	        }
			
	        return match;
	    }
	}

这个类的主要作用就是计算并缓存用户尝试登陆的次数,如果大于了5次,那么该用户将被禁止登陆直到10分钟以后。这个时间在ehcache.xml中timeToIdleSeconds设置。

5.ShiroConfig.java

//配置文件注解
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        //filterChainDefinitionMap.put("/userInfo/userList", "userInfo:view");
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * )
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        //myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        //匹配新创建管理匹配器
        myShiroRealm.setCredentialsMatcher(retryLimitCredentialsMatcher());
        return myShiroRealm;
    }


    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
        mappings.setProperty("UnauthorizedException","403");
        r.setExceptionMappings(mappings);  // None by default
        r.setDefaultErrorView("error");    // No default
        r.setExceptionAttribute("ex");     // Default is "exception"
        //r.setWarnLogCategory("example.MvcLogger");     // No default
        return r;
    }

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
        return ehCacheManager;
    }

    @Bean
    public CredentialsMatcher retryLimitCredentialsMatcher() {
        RetryLimitCredentialsMatcher retryLimitCredentialsMatcher = new RetryLimitCredentialsMatcher(this.ehCacheManager());
        retryLimitCredentialsMatcher.setHashAlgorithmName("md5");
        retryLimitCredentialsMatcher.setHashIterations(2);
        retryLimitCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return retryLimitCredentialsMatcher;
    }
}

6.SpringEhcacheShutdownListener.java

@Component
public class SpringEhcacheShutdownListener implements ApplicationListener<ApplicationEvent>{
 
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
            ApplicationContext context = ((ContextClosedEvent) event).getApplicationContext();
            EhCacheManager ehCacheManager = (EhCacheManager) context.getBean("ehCacheManager");
            if (ehCacheManager != null) {
                ehCacheManager.destroy();
            }
        }
    }
}
配置每次登出或者重启之后,清空缓存

7. HomeController

@Controller
public class HomeController {

    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }

    @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
        System.out.println("HomeController.login()");
        // 登录失败从request中获取shiro处理的异常信息。
        // shiroLoginFailure:就是shiro异常类的全类名.
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception=" + exception);
        String msg = "";
        if (exception!= null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                System.out.println("UnknownAccountException -- > 账号不存在:");
                msg = "UnknownAccountException -- > 账号不存在:";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                System.out.println("IncorrectCredentialsException -- > 密码不正确:");
                msg = "IncorrectCredentialsException -- > 密码不正确:";
            } else if ("kaptchaValidateFailed".equals(exception)) {
                System.out.println("kaptchaValidateFailed -- > 验证码错误");
                msg = "kaptchaValidateFailed -- > 验证码错误";
            }else if (ExcessiveAttemptsException.class.getName().equals(exception)) {
                System.out.println("ExcessiveAttemptsException -- >  密码错误次数过多,已被锁定,请稍后重试");
                msg = "ExcessiveAttemptsException -- > 密码错误次数过多,已被锁定,请稍后重试";
            } else {
                msg = "else >> "+exception;
                System.out.println("else -- >" + exception);
            }
        }
        map.put("msg", msg);
        // 此方法不处理登录成功,由shiro进行处理
        return "login";
    }

    @RequestMapping("/403")
    public String unauthorizedRole(){
        System.out.println("------没有权限-------");
        return "403";
    }

}

测试

连续登陆你设置的次数,就会报登录次数太多

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 安装mysql出现提示MSVCR120.dll文件丢失

    解决方法:到微软官网下载 VC redist packages for x64 跳转

    微醺
  • 日志切面的配置使用

    在User类上加上@Component(value=“user”)//注入beanfactory中

    微醺
  • 加考试的锁,设置失效时间

    微醺
  • 基于 RxJava2+Retrofit2 精心打造的 Android 基础框架 XSnow

    XSnow ? 基于RxJava2+Retrofit2精心打造的Android基础框架,包含网络、上传、下载、缓存、事件总线、权限管理、数据库、图片加载、UI模...

    非著名程序员
  • Android中volley封装实践记录

    在项目中一般使用使用volley方式如下,用起来给人一种很乱的感觉,于是一种盘它的想法油然而生。

    砸漏
  • android离线缓存技术

    离线缓存是指在有网络的状态下将从服务器获取的网络数据,如Json 数据缓存到本地,在断网的状态下启动APP时读取本地缓存数据显示在界面上,常用的APP(网易新...

    xiangzhihong
  • Android基于MediaBroswerService的App实现概述

    如何实现一个音乐播放App,然后让其可以被第三方的Android app打开,并获取其中的歌单,曲目列表,同时控制其播放呢?现有应用市场上,已经有相应的实现。比...

    Android技术干货分享
  • RxAndroid从零开始学之五(常见操作符与三级缓存)

    RxAndroid的操作符有很多,本以为写了上一节的一些基本的Operator就可以正常编写代码了,但是后来在github上看googlesample,发现了一...

    Frank909
  • Netty实现简单RPC调用

    我们知道Dubbo是一个RPC框架,那RPC框架需要实现什么?需要实现的是调用远程服务和本地服务一样方便,同时提高调用远程服务的性能。而服务端和客户端之间的关系...

    路行的亚洲
  • Java基础系列(四十二):集合之AbstractList

    AbstractList是实现List接口的一个抽象类,它的地位之与List类似于AbstractCollection之与Collection,同事,Abstr...

    山禾说

扫码关注云+社区

领取腾讯云代金券