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
#数据库连接
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
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;
}
}
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;
}
}
@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";
}
输出错误的密码登录,每次登录redis中数据加1
当错误的次数达到指定次数就锁定,锁定期间输入正确密码登录无效,锁定时间结束后输入正确密码则登录成功并清除登录次数