package com.comtop.map.pub.security;
import com.comtop.map.pub.cache.redis.JedisTemplate;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
private static final Logger log = LoggerFactory.getLogger(RetryLimitCredentialsMatcher.class);
private static final String NAMESPACE = "passwordRetryCache:";
private JedisTemplate jedisTemplate;
public RetryLimitCredentialsMatcher(JedisTemplate jedisTemplate) {
this.jedisTemplate = jedisTemplate;
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
RestUsernamePasswordToken rupToken = (RestUsernamePasswordToken) token;
String source = rupToken.getSource();
String loginType = rupToken.getLoginType();
boolean pcLogin = RestUsernamePasswordToken.LOGIN_SOURCE_PC.equals(source) && RestUsernamePasswordToken.LOGIN_SOURCE_PC.equals(loginType);
if (!pcLogin) {
return super.doCredentialsMatch(token, info);
}
String username = (String) token.getPrincipal();
String redisKey = NAMESPACE + username;
Object retryCount = jedisTemplate.getObject(redisKey);
if (retryCount == null) {
retryCount = 0;
jedisTemplate.setexObject(redisKey, 600, retryCount);
}
int max = 5;
int current = (int) retryCount + 1;
if (current > max) {
log.warn("username: " + username + " tried to login more than 5 times in period");
throw new ExcessiveAttemptsException("您的用户名或密码错误次数已满,将锁定10分钟。");
}
jedisTemplate.setexObject(redisKey, 600, (int) retryCount + 1);
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
//clear retry data
jedisTemplate.del(redisKey);
} else {
int remain = max - current;
if (remain == 0) {
throw new ExcessiveAttemptsException("您的用户名或密码错误次数已满,将锁定10分钟。");
} else {
throw new ExcessiveAttemptsException("您的用户名或密码错误,还有" + remain + "次机会。");
}
}
return true;
}
/**
* 根据用户名 解锁用户
*/
public void unlockAccount(String username){
jedisTemplate.del(NAMESPACE + username);
}
}
package com.comtop.map.pub.config;
import com.comtop.map.pub.cache.redis.JedisTemplate;
import com.comtop.map.pub.security.Passwords;
import com.comtop.map.pub.security.RetryLimitCredentialsMatcher;
import com.comtop.map.pub.security.shiro.*;
import com.comtop.map.pub.security.shiro.cookie.CustomSysCookie;
import com.comtop.map.pub.security.shiro.session.AngWebSessionManager;
import com.comtop.map.pub.security.shiro.session.CustomSysSessionFactory;
import com.comtop.map.pub.security.shiro.subject.CustomSysSubjectFactory;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("shiroDbRealm") ShiroDbRealm shiroDbRealm,
@Qualifier("defaultWebSessionManager") SessionManager sessionManager,
@Qualifier("customShiroCacheManager") CacheManager cacheManager,
@Qualifier("customSysSubjectFactory") CustomSysSubjectFactory subjectFactory) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setSessionManager(sessionManager);
manager.setRealm(shiroDbRealm);
manager.setCacheManager(cacheManager);
manager.setSubjectFactory(subjectFactory);
return manager;
}
@Bean(name = "customSysSubjectFactory")
public CustomSysSubjectFactory customSysSubjectFactory() {
return new CustomSysSubjectFactory();
}
@Bean(name = "shiroDbRealm")
public ShiroDbRealm shiroDbRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher credentialsMatcher) {
ShiroDbRealm realm = new ShiroDbRealm();
realm.setCredentialsMatcher(credentialsMatcher);
return realm;
}
@Bean(name = "defaultWebSessionManager")
public SessionManager sessionManager(@Qualifier("customSysSessionFactory") CustomSysSessionFactory sessionFactory,
@Qualifier("customSessionIdCookie") Cookie sessionIdCookie,
@Qualifier("customShiroSessionDAO") SessionDAO sessionDAO,
@Value("${shiro.session.timeout:300000}") int sessionTimeout) {
AngWebSessionManager sessionManager = new AngWebSessionManager();
sessionManager.setGlobalSessionTimeout(sessionTimeout);
sessionManager.setSessionIdCookie(sessionIdCookie);
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionFactory(sessionFactory);
return sessionManager;
}
@Bean(name = "customSysSessionFactory")
public CustomSysSessionFactory customSysSessionFactory(){
return new CustomSysSessionFactory();
}
@Bean(name = "customSessionIdCookie")
public Cookie cookie() {
CustomSysCookie simpleCookie = new CustomSysCookie("Agrant");
simpleCookie.setPath("/");
return simpleCookie;
}
@Bean(name = "customShiroSessionDAO")
public SessionDAO sessionDAO(@Qualifier("shiroSessionRepository") ShiroSessionRepository shiroSessionRepository,
@Qualifier("sessionIdGenerator") SessionIdGenerator sessionIdGenerator) {
CustomShiroSessionDAO sessionDAO = new CustomShiroSessionDAO();
sessionDAO.setShiroSessionRepository(shiroSessionRepository);
sessionDAO.setSessionIdGenerator(sessionIdGenerator);
return sessionDAO;
}
@Bean(name = "sessionIdGenerator")
public SessionIdGenerator sessionIdGenerator() {
return new CustomSessionIdGenerator();
}
@Bean(name = "shiroSessionRepository")
public ShiroSessionRepository shiroSessionRepository(JedisTemplate jedisTemplate) {
return new JedisShiroSessionRepository(jedisTemplate);
}
@Bean(name = "customShiroCacheManager")
public CacheManager cacheManager(@Qualifier("jedisShiroCacheManager") ShiroCacheManager shiroCacheManager) {
CustomShiroCacheManager cacheManager = new CustomShiroCacheManager();
cacheManager.setShiroCacheManager(shiroCacheManager);
return cacheManager;
}
@Bean(name = "jedisShiroCacheManager")
public ShiroCacheManager shiroCacheManager(JedisTemplate jedisTemplate) {
return new JedisShiroCacheManager(jedisTemplate);
}
@Bean(name = "credentialsMatcher")
public RetryLimitCredentialsMatcher hashedCredentialsMatcher(JedisTemplate jedisTemplate) {
// 设定Password校验的Hash算法与迭代次数.
RetryLimitCredentialsMatcher credentialsMatcher = new RetryLimitCredentialsMatcher(jedisTemplate);
credentialsMatcher.setHashAlgorithmName(Passwords.HASH_ALGORITHM);
credentialsMatcher.setHashIterations(Passwords.HASH_INTERATIONS);
return credentialsMatcher;
}
@Bean("managePerms")
public ManagePerms managePerms(){
return new ManagePerms();
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager,
@Qualifier("managePerms") ManagePerms managePerms,
@Qualifier("restAnonymousFilter") Filter restApi) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(manager);
shiroFilterFactoryBean.setLoginUrl("/login/timeout");
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
Map<String, Filter> filters = new HashMap<>();
filters.put("restApi", restApi);
shiroFilterFactoryBean.setFilters(filters);
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/ueditor/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/docs.html", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/captcha", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/unauthorized", "anon");
filterChainDefinitionMap.put("/api/download/history", "user");
filterChainDefinitionMap.putAll(managePerms.getUri());
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/admin/**", "restApi");
filterChainDefinitionMap.put("/developer/**", "restApi");
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "restAnonymousFilter")
public Filter restAnonymousFilter() {
return new RestAnonymousFilter();
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
package com.comtop.map.pub.security;
import com.comtop.map.pub.security.shiro.CaptchaUsernamePasswordToken;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.apache.commons.lang3.StringUtils;
/**
* rest服务登录新增客户端id字段
*/
@JsonIgnoreProperties(value = { "password", "principal", "credentials", "tenantId" })
public class RestUsernamePasswordToken extends CaptchaUsernamePasswordToken {
/** 登陆来源 pc端 */
public static final String LOGIN_SOURCE_PC = "PC";
/** 登陆来源 手机端 */
public static final String LOGIN_SOURCE_MOBILE = "MOBILE";
/** 默认使用用户名或密码方式登录 */
public static final String LOGIN_TYPE_DEFAULT = "DEFAULT";
/** 4A账号登陆 */
public static final String LOGIN_TYPE_4A = "4A";
/** 短信验证码登陆 */
public static final String LOGIN_TYPE_SMSCODE = "SMS_CODE";
/** 序列串 */
private stati