SpringBoot+Redis实现账号锁定功能

1、加入需要用到的包


        
            org.apache.shiro
            shiro-spring
            1.4.1
        
        
            org.crazycake
            shiro-redis
            3.1.0
        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            org.springframework.session
            spring-session-data-redis
        
        
            org.apache.commons
            commons-pool2
        
        
            nz.net.ultraq.thymeleaf
            thymeleaf-layout-dialect
        

        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

2、编写application.properties,配置连接数据库和Redis的信息

#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#JPA设置
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.open-in-view=true

spring.thymeleaf.cache=false

#Redis连接信息
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器超时时间(毫秒)
spring.redis.timeout=5000
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)默认-1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

3、编写MyShiroRealm文件

package com.demo.loginlock.config;

import com.demo.loginlock.entity.User;
import com.demo.loginlock.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

public class MyShiroRealm extends AuthorizingRealm {
    @Resource
    private UserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //用户登录次数计数  redisKey 前缀
    private String SHIRO_LOGIN_COUNT = "shiro_login_count_";

    //用户登录是否被锁定  redisKey 前缀
    private String SHIRO_IS_LOCK = "shiro_is_lock_";


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("1:身份认证-->MyShiroRealm.doGetAuthorizationInfo()");
        UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
        String userName=token.getUsername();
        String userPassword=new String(token.getPassword());
        System.out.println("usrName:"+userName);
        System.out.println("usrPassword:"+userPassword);

        //访问一次,计数一次
        ValueOperations opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.increment(SHIRO_LOGIN_COUNT+userName, 1);  //每次增加1
        System.out.println(userName+":账号登陆的次数是:"+opsForValue.get(SHIRO_LOGIN_COUNT+userName)) ;

        //如果这个账号登陆异常,则在登陆页面提醒。
        if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+userName))>=3) {
            if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + userName))) {
                //计数大于3次,设置用户被锁定一分钟
                throw new DisabledAccountException("由于输入错误次数大于3次,帐号1分钟内已经禁止登录!");
            }
        }
        //实现锁定
        if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+userName))>=3){
            opsForValue.set(SHIRO_IS_LOCK+userName, "LOCK");  //锁住这个账号,值是LOCK。
            stringRedisTemplate.expire(SHIRO_IS_LOCK+userName, 1, TimeUnit.MINUTES);  //expire  变量存活期限
        }

        //根据用户名去数据库查询
        User user=userService.getUser(userName);
        if(user==null){
            throw new UnknownAccountException("账号不存在!");
        }else if(!user.getUsrPassword().equals(userPassword)){
            throw new IncorrectCredentialsException("密码不正确!");
        }

        SimpleAuthenticationInfo authorizationInfo=new SimpleAuthenticationInfo(user,user.getUsrPassword(),getName());

        //清空登录计数
        opsForValue.set(SHIRO_LOGIN_COUNT+userName, "0");
        //清空锁
        opsForValue.set(SHIRO_IS_LOCK+userName, "");

        return authorizationInfo;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("2:权限认证-->MyShiroRealm.doGetAuthorizationInfo()");

        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        return authorizationInfo;
    }
}

4、编写ShiroConfig文件

package com.demo.loginlock.config;

import com.demo.loginlock.service.UserService;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig{
    @Resource
    private UserService userService;

    @Bean
    public SecurityManager securityManager(){
        System.out.println("1:securityManager..........");
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //设置realm
        securityManager.setRealm(myShiroRealm());

        return securityManager;
    }

    @Bean
    public MyShiroRealm myShiroRealm(){
        System.out.println("2:myShiroRealm..........");
        MyShiroRealm myShiroRealm=new MyShiroRealm();
        return myShiroRealm;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        System.out.println("3:ShiroConfiguration.shiroFilter():配置权限控制规则");
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        
        //登录提交地址
        shiroFilterFactoryBean.setLoginUrl("/login");
        //访问没有授权的资源
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        //拦截器
        Map filtrChainDefinitionMap=new LinkedHashMap();
        //匿名可以访问的地址
        filtrChainDefinitionMap.put("/dologin","anon");
        filtrChainDefinitionMap.put("/css/**","anon");
        filtrChainDefinitionMap.put("/fonts/**","anon");
        filtrChainDefinitionMap.put("/images/**","anon");
        filtrChainDefinitionMap.put("/js/**","anon");
        filtrChainDefinitionMap.put("/localcss/**","anon");
        //配置退出(记住我状态下,可清除记住我的cookie)
        filtrChainDefinitionMap.put("/logout","logout");

        //所有路径必须授权访问(登录),且必须放在最后
        filtrChainDefinitionMap.put("/**","user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filtrChainDefinitionMap);

        return shiroFilterFactoryBean;
    }
}


5、编写控制器登录请求

@RequestMapping(value = "/dologin")
    public String login(String usrName, String usrPassword, Map map, HttpSession session, HttpServletRequest request){
        System.out.println("dologin......");
        try{
            AuthenticationToken token=new UsernamePasswordToken(usrName,usrPassword);
            //调用Shiro进行认证
            SecurityUtils.getSubject().login(token);
            //从Shiro中拿出User对象
            User user=(User)SecurityUtils.getSubject().getPrincipal();
            session.setAttribute("user",user);
            System.out.println("登录成功!");
        }catch (Exception e){
            map.put("msg",e.getMessage());
            return "login";
        }
        return "redirect:/toMain";
    }

6、测试

输出错误的密码登录,每次登录redis中数据加1
SpringBoot+Redis实现账号锁定功能_第1张图片
SpringBoot+Redis实现账号锁定功能_第2张图片
当错误的次数达到指定次数就锁定,锁定期间输入正确密码登录无效,锁定时间结束后输入正确密码则登录成功并清除登录次数
SpringBoot+Redis实现账号锁定功能_第3张图片SpringBoot+Redis实现账号锁定功能_第4张图片

你可能感兴趣的:(SpringBoot)