本文主要引入的jar包如下:
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-ehcache
1.4.0
由于我们将使用shiro + ehache配合使用,所以可以不用单独再引用ehcache.jar了,使用shiro-ehcache时,会自动添加ehcache-core 2.6.11。
resources
config
ehcache.xml
关于以上属性代表的含义,可以在官方文档中找到官方文档
配置中不能对ehcache标签添加monitoring=“autodetect”,否侧缓存将无法保存。
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 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;
}
}
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher{
private static final int MAX_LOGIN_RETRY_TIMES = 5;
private Cache 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设置。
//配置文件注解
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//:这是一个坑呢,一不小心代码就不好使了;
//
//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;
}
}
@Component
public class SpringEhcacheShutdownListener implements ApplicationListener{
@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();
}
}
}
}
配置每次登出或者重启之后,清空缓存
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map 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";
}
}
连续登陆你设置的次数,就会报登录次数太多