1、加入需要用到的包
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
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<String, String> 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<String,String> filtrChainDefinitionMap=new LinkedHashMap<String,String>();
//匿名可以访问的地址
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<String,Object> 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
当错误的次数达到指定次数就锁定,锁定期间输入正确密码登录无效,锁定时间结束后输入正确密码则登录成功并清除登录次数