用户尝试登陆错误次数

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";
    }

}

测试

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

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43580281/article/details/85250256
今日推荐