文章目录
案例代码来自ruoyi管理系统的shiro部分
知识点介绍
AccessControlFilter
AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:
isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
onPreHandle会自动调用这两个方法决定是否继续处理:
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
配置过滤器的步骤:
1.继承AccessControlFilter
2.重写isAccessAllowed onAccessDenied(有时也需要重写onPreHandle等其他方法)
3.在shiroconfig中配置自己写的过滤器类,并加上@Bean注解,并配置过滤器链:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
.......
(定时任务调度的使用):
4.配置会话管理器加入自定义的任务调度器
/**
* 会话管理器
*/
@Bean
public OnlineWebSessionManager sessionManager()
{
OnlineWebSessionManager manager = new OnlineWebSessionManager();
// 加入缓存管理器
manager.setCacheManager(getEhCacheManager());
// 删除过期的session
manager.setDeleteInvalidSessions(true);
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
// 去掉 JSESSIONID
manager.setSessionIdUrlRewritingEnabled(false);
// 定义要使用的无效的Session定时调度器
manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
// 是否定时检查session
manager.setSessionValidationSchedulerEnabled(true);
// 自定义SessionDao
manager.setSessionDAO(sessionDAO());
// 自定义sessionFactory
manager.setSessionFactory(sessionFactory());
return manager;
}
验证码过滤器
public class CaptchaValidateFilter extends AccessControlFilter
{
/**
* 是否开启验证码
*/
private boolean captchaEnabled = true;
/**
* 验证码类型
*/
private String captchaType = "math";
public void setCaptchaEnabled(boolean captchaEnabled)
{
this.captchaEnabled = captchaEnabled;
}
public void setCaptchaType(String captchaType)
{
this.captchaType = captchaType;
}
@Override
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
{
request.setAttribute(ShiroConstants.CURRENT_ENABLED, captchaEnabled);
request.setAttribute(ShiroConstants.CURRENT_TYPE, captchaType);
return super.onPreHandle(request, response, mappedValue);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception
{
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 验证码禁用 或不是表单提交 允许访问
if (captchaEnabled == false || !"post".equals(httpServletRequest.getMethod().toLowerCase()))
{
return true;
}
return validateResponse(httpServletRequest, httpServletRequest.getParameter(ShiroConstants.CURRENT_VALIDATECODE));
}
public boolean validateResponse(HttpServletRequest request, String validateCode)
{
Object obj = ShiroUtils.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
String code = String.valueOf(obj != null ? obj : "");
// 验证码清除,防止多次使用。
request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY);
if (StringUtils.isEmpty(validateCode) || !validateCode.equalsIgnoreCase(code))
{
return false;
}
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
{
request.setAttribute(ShiroConstants.CURRENT_CAPTCHA, ShiroConstants.CAPTCHA_ERROR);
return true;
}
}
登录账号控制过滤器
public class KickoutSessionFilter extends AccessControlFilter
{
private final static ObjectMapper objectMapper = new ObjectMapper();
/**
* 同一个用户最大会话数
**/
private int maxSession = -1;
/**
* 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户
**/
private Boolean kickoutAfter = false;
/**
* 踢出后到的地址
**/
private String kickoutUrl;
private SessionManager sessionManager;
private Cache<String, Deque<Serializable>> cache;
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)
throws Exception
{
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
{
Subject subject = getSubject(request, response);
if (!subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1)
{
// 如果没有登录或用户最大会话数为-1,直接进行之后的流程
return true;
}
try
{
Session session = subject.getSession();
// 当前登录用户
SysUser user = ShiroUtils.getSysUser();
String loginName = user.getLoginName();
Serializable sessionId = session.getId();
// 读取缓存用户 没有就存入
Deque<Serializable> deque = cache.get(loginName);
if (deque == null)
{
// 初始化队列
deque = new ArrayDeque<Serializable>();
}
// 如果队列里没有此sessionId,且用户没有被踢出;放入队列
if (!deque.contains(sessionId) && session.getAttribute("kickout") == null)
{
// 将sessionId存入队列
deque.push(sessionId);
// 将用户的sessionId队列缓存
cache.put(loginName, deque);
}
// 如果队列里的sessionId数超出最大会话数,开始踢人
while (deque.size() > maxSession)
{
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast();
// 踢出后再更新下缓存队列
cache.put(loginName, deque);
try
{
// 获取被踢出的sessionId的session对象
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if (null != kickoutSession)
{
// 设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
}
catch (Exception e)
{
// 面对异常,我们选择忽略
}
}
// 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null && (Boolean) session.getAttribute("kickout") == true)
{
// 退出登录
subject.logout();
saveRequest(request);
return isAjaxResponse(request, response);
}
return true;
}
catch (Exception e)
{
return isAjaxResponse(request, response);
}
}
private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException
{
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (ServletUtils.isAjaxRequest(req))
{
AjaxResult ajaxResult = AjaxResult.error("您已在别处登录,请您修改密码或重新登录");
ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult));
}
else
{
WebUtils.issueRedirect(request, response, kickoutUrl);
}
return false;
}
public void setMaxSession(int maxSession)
{
this.maxSession = maxSession;
}
public void setKickoutAfter(boolean kickoutAfter)
{
this.kickoutAfter = kickoutAfter;
}
public void setKickoutUrl(String kickoutUrl)
{
this.kickoutUrl = kickoutUrl;
}
public void setSessionManager(SessionManager sessionManager)
{
this.sessionManager = sessionManager;
}
// 设置Cache的key的前缀
public void setCacheManager(CacheManager cacheManager)
{
// 必须和ehcache缓存配置中的缓存name一致
this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE);
}
自定义访问控制过滤器
public class OnlineSessionFilter extends AccessControlFilter
{
/**
* 强制退出后重定向的地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
private OnlineSessionDAO onlineSessionDAO;
/**
* 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception
{
Subject subject = getSubject(request, response);
if (subject == null || subject.getSession() == null)
{
return true;
}
Session session = onlineSessionDAO.readSession(subject.getSession().getId());
if (session != null && session instanceof OnlineSession)
{
OnlineSession onlineSession = (OnlineSession) session;
request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession);
// 把user对象设置进去
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
if (isGuest == true)
{
SysUser user = ShiroUtils.getSysUser();
if (user != null)
{
onlineSession.setUserId(user.getUserId());
onlineSession.setLoginName(user.getLoginName());
onlineSession.setAvatar(user.getAvatar());
onlineSession.setDeptName(user.getDept().getDeptName());
onlineSession.markAttributeChanged();
}
}
if (onlineSession.getStatus() == OnlineStatus.off_line)
{
return false;
}
}
return true;
}
/**
* 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
{
Subject subject = getSubject(request, response);
if (subject != null)
{
subject.logout();
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
// 跳转到登录页
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
{
WebUtils.issueRedirect(request, response, loginUrl);
}
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
{
this.onlineSessionDAO = onlineSessionDAO;
}
}
同步Sesion到数据库
public class SyncOnlineSessionFilter extends PathMatchingFilter
{
//PathMatchingFilter提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,
// 如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:
private OnlineSessionDAO onlineSessionDAO;
/**
* 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前
*/
//onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,
// 会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;
// 然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;
// 也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)。
//
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
{
OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);
// 如果session stop了 也不同步
// session停止时间,如果stopTimestamp不为null,则代表已停止
if (session != null && session.getUserId() != null && session.getStopTimestamp() == null)
{
onlineSessionDAO.syncToDb(session);
}
return true;
}
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
{
this.onlineSessionDAO = onlineSessionDAO;
}
}
退出过滤器
public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
{
private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);
/**
* 退出后重定向的地址
*/
private String loginUrl;
public String getLoginUrl()
{
return loginUrl;
}
public void setLoginUrl(String loginUrl)
{
this.loginUrl = loginUrl;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
{
try
{
Subject subject = getSubject(request, response);
String redirectUrl = getRedirectUrl(request, response, subject);
try
{
SysUser user = ShiroUtils.getSysUser();
if (StringUtils.isNotNull(user))
{
String loginName = user.getLoginName();
// 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
// 清理缓存
SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId());
}
// 退出登录
subject.logout();
}
catch (SessionException ise)
{
log.error("logout fail.", ise);
}
issueRedirect(request, response, redirectUrl);
}
catch (Exception e)
{
log.error("Encountered session exception during logout. This can generally safely be ignored.", e);
}
return false;
}
/**
* 退出跳转URL
*/
@Override
protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject)
{
String url = getLoginUrl();
if (StringUtils.isNotEmpty(url))
{
return url;
}
return super.getRedirectUrl(request, response, subject);
}
}
定期session验证任务调度器
@Component
public class SpringSessionValidationScheduler implements SessionValidationScheduler
{
private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class);
public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
/**
* 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。
*/
@Autowired
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService executorService;
private volatile boolean enabled = false;
/**
* 会话验证管理器
*/
@Autowired
@Qualifier("sessionManager")
@Lazy
private ValidatingSessionManager sessionManager;
// 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
@Value("${shiro.session.validationInterval}")
private long sessionValidationInterval;
@Override
public boolean isEnabled()
{
return this.enabled;
}
//设置定期验证session是否过期的时间间隔
public void setSessionValidationInterval(long sessionValidationInterval)
{
this.sessionValidationInterval = sessionValidationInterval;
}
/**
* Starts session validation by creating a spring PeriodicTrigger.
*/
@Override
public void enableSessionValidation()
{
enabled = true;
if (log.isDebugEnabled())
{
log.debug("Scheduling session validation job using Spring Scheduler with "
+ "session validation interval of [" + sessionValidationInterval + "]ms...");
}
try
{
executorService.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
if (enabled)
{
sessionManager.validateSessions();
//org.apache.shiro.session.mgt包下的方法validateSessions();
//对系统中所有打开/活动会话(那些尚未停止或过期的会话)进行会话验证,并验证每个会话。
// 如果发现一个会话无效(例如它已经过期),它会被更新并保存到 EIS。
}
}
}, 1000, sessionValidationInterval * 60 * 1000, TimeUnit.MILLISECONDS);
this.enabled = true;
if (log.isDebugEnabled())
{
log.debug("Session validation job successfully scheduled with Spring Scheduler.");
}
}
catch (Exception e)
{
if (log.isErrorEnabled())
{
log.error("Error starting the Spring Scheduler session validation job. Session validation may not occur.", e);
}
}
}
@Override
public void disableSessionValidation()
{
if (log.isDebugEnabled())
{
log.debug("Stopping Spring Scheduler session validation job...");
}
if (this.enabled)
{
Threads.shutdownAndAwaitTermination(executorService);
}
this.enabled = false;
}
}
shiro配置类
创建一个ShiroConfig,然后创建一个shiroFilter方法。在Shiro使用认证和授权时,其实都是通过ShiroFilterFactoryBean设置一些Shiro的拦截器进行的,拦截器会以LinkedHashMap的形式存储需要拦截的资源及链接,并且会按照顺序执行,其中键为拦截的资源或链接,值为拦截的形式(比如authc:所有URL都必须认证通过才可以访问,anon:所有URL都可以匿名访问),在拦截的过程中可以使用通配符,比如/**为拦截所有,所以一般/**放在最下面。同时,可以通过ShiroFilterFactoryBean设置登录链接、未授权链接、登录成功跳转页等,这里设置的shiroFilter方法内容如代码清单7-7所示。
@Configuration
public class ShiroConfig
{
/**
* Session超时时间,单位为毫秒(默认30分钟)
*/
@Value("${shiro.session.expireTime}")
private int expireTime;
/**
* 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
*/
@Value("${shiro.session.validationInterval}")
private int validationInterval;
/**
* 同一个用户最大会话数
*/
@Value("${shiro.session.maxSession}")
private int maxSession;
/**
* 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
*/
@Value("${shiro.session.kickoutAfter}")
private boolean kickoutAfter;
/**
* 验证码开关
*/
@Value("${shiro.user.captchaEnabled}")
private boolean captchaEnabled;
/**
* 验证码类型
*/
@Value("${shiro.user.captchaType}")
private String captchaType;
/**
* 设置Cookie的域名
*/
@Value("${shiro.cookie.domain}")
private String domain;
/**
* 设置cookie的有效访问路径
*/
@Value("${shiro.cookie.path}")
private String path;
/**
* 设置HttpOnly属性
*/
@Value("${shiro.cookie.httpOnly}")
private boolean httpOnly;
/**
* 设置Cookie的过期时间,秒为单位
*/
@Value("${shiro.cookie.maxAge}")
private int maxAge;
/**
* 设置cipherKey密钥
*/
@Value("${shiro.cookie.cipherKey}")
private String cipherKey;
/**
* 登录地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
/**
* 权限认证失败地址
*/
@Value("${shiro.user.unauthorizedUrl}")
private String unauthorizedUrl;
/**
* 是否开启记住我功能
*/
@Value("${shiro.rememberMe.enabled: false}")
private boolean rememberMe;
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public EhCacheManager getEhCacheManager()
{
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
EhCacheManager em = new EhCacheManager();
if (StringUtils.isNull(cacheManager))
{
em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
return em;
}
else
{
em.setCacheManager(cacheManager);
return em;
}
}
/**
* 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署
*/
protected InputStream getCacheManagerConfigFileInputStream()
{
String configFile = "classpath:ehcache/ehcache-shiro.xml";
InputStream inputStream = null;
try
{
inputStream = ResourceUtils.getInputStreamForPath(configFile);
byte[] b = IOUtils.toByteArray(inputStream);
InputStream in = new ByteArrayInputStream(b);
return in;
}
catch (IOException e)
{
throw new ConfigurationException(
"Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
}
finally
{
IOUtils.closeQuietly(inputStream);
}
}
/**
* 自定义Realm
*/
@Bean
public UserRealm userRealm(EhCacheManager cacheManager)
{
UserRealm userRealm = new UserRealm();
userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE);
userRealm.setCacheManager(cacheManager);
return userRealm;
}
/**
* 自定义sessionDAO会话
*/
@Bean
public OnlineSessionDAO sessionDAO()
{
OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
return sessionDAO;
}
/**
* 自定义sessionFactory会话
*/
@Bean
public OnlineSessionFactory sessionFactory()
{
OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
return sessionFactory;
}
/**
* 会话管理器
*/
@Bean
public OnlineWebSessionManager sessionManager()
{
OnlineWebSessionManager manager = new OnlineWebSessionManager();
// 加入缓存管理器
manager.setCacheManager(getEhCacheManager());
// 删除过期的session
manager.setDeleteInvalidSessions(true);
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
// 去掉 JSESSIONID
manager.setSessionIdUrlRewritingEnabled(false);
// 定义要使用的无效的Session定时调度器
manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
// 是否定时检查session
manager.setSessionValidationSchedulerEnabled(true);
// 自定义SessionDao
manager.setSessionDAO(sessionDAO());
// 自定义sessionFactory
manager.setSessionFactory(sessionFactory());
return manager;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(UserRealm userRealm)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(userRealm);
// 记住我
securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 退出过滤器
*/
public LogoutFilter logoutFilter()
{
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setLoginUrl(loginUrl);
return logoutFilter;
}
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/ruoyi.png**", "anon");
filterChainDefinitionMap.put("/html/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/ajax/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/ruoyi/**", "anon");
filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
// 退出 logout地址,shiro去清除session
filterChainDefinitionMap.put("/logout", "logout");
// 不需要拦截的访问
filterChainDefinitionMap.put("/login", "anon,captchaValidate");
// 注册相关
filterChainDefinitionMap.put("/register", "anon,captchaValidate");
// 系统权限列表
// filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter());
filters.put("kickout", kickoutSessionFilter());
// 注销成功,则跳转到指定页面
filters.put("logout", logoutFilter());
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 自定义在线用户处理过滤器
*/
public OnlineSessionFilter onlineSessionFilter()
{
OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
onlineSessionFilter.setLoginUrl(loginUrl);
onlineSessionFilter.setOnlineSessionDAO(sessionDAO());
return onlineSessionFilter;
}
/**
* 自定义在线用户同步过滤器
*/
public SyncOnlineSessionFilter syncOnlineSessionFilter()
{
SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO());
return syncOnlineSessionFilter;
}
/**
* 自定义验证码过滤器
*/
public CaptchaValidateFilter captchaValidateFilter()
{
CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();
captchaValidateFilter.setCaptchaEnabled(captchaEnabled);
captchaValidateFilter.setCaptchaType(captchaType);
return captchaValidateFilter;
}
/**
* cookie 属性设置
*/
public SimpleCookie rememberMeCookie()
{
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setHttpOnly(httpOnly);
cookie.setMaxAge(maxAge * 24 * 60 * 60);
return cookie;
}
/**
* 记住我
*/
public CookieRememberMeManager rememberMeManager()
{
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
if (StringUtils.isNotEmpty(cipherKey))
{
cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey));
}
else
{
cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded());
}
return cookieRememberMeManager;
}
/**
* 同一个用户多设备登录限制
*/
public KickoutSessionFilter kickoutSessionFilter()
{
KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
kickoutSessionFilter.setCacheManager(getEhCacheManager());
kickoutSessionFilter.setSessionManager(sessionManager());
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
kickoutSessionFilter.setMaxSession(maxSession);
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
kickoutSessionFilter.setKickoutAfter(kickoutAfter);
// 被踢出后重定向到的地址;
kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
return kickoutSessionFilter;
}
/**
* thymeleaf模板引擎和shiro框架的整合
*/
@Bean
public ShiroDialect shiroDialect()
{
return new ShiroDialect();
}
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
自定义Session类(继承 SimpleSession)
public class OnlineSession extends SimpleSession
{
private static final long serialVersionUID = 1L;
/** 用户ID */
private Long userId;
/** 用户名称 */
private String loginName;
/** 部门名称 */
private String deptName;
/** 用户头像 */
private String avatar;
/** 登录IP地址 */
private String host;
/** 浏览器类型 */
private String browser;
/** 操作系统 */
private String os;
/** 在线状态 */
private OnlineStatus status = OnlineStatus.on_line;
/** 属性是否改变 优化session数据同步 */
private transient boolean attributeChanged = false;
@Override
public String getHost()
{
return host;
}
@Override
public void setHost(String host)
{
this.host = host;
}
自定义SessionFactory会话(继承SessionFactory)
@Data
@Component
public class OnlineSessionFactory implements SessionFactory
{
@Override
public Session createSession(SessionContext initData)
{
OnlineSession session = new OnlineSession();
if (initData != null && initData instanceof WebSessionContext)
{
WebSessionContext sessionContext = (WebSessionContext) initData;
HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();
if (request != null)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
session.setHost(IpUtils.getIpAddr(request));
session.setBrowser(browser);
session.setOs(os);
}
}
return session;
}
}
自定义的ShiroSessionDao(继承EnterpriseCacheSessionDAO)
public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
{
//extends AbstractSessionDAO
/**
* 同步session到数据库的周期 单位为毫秒(默认1分钟)
*/
@Value("${shiro.session.dbSyncPeriod}")
private int dbSyncPeriod;
/**
* 上次同步数据库的时间戳
*/
private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP";
@Autowired
private SysShiroService sysShiroService;
public OnlineSessionDAO()
{
super();
}
public OnlineSessionDAO(long expireTime)
{
super();
}
/**
* 根据会话ID获取会话
*
* @param sessionId 会话ID
* @return ShiroSession
*/
@Override
protected Session doReadSession(Serializable sessionId)
{
return sysShiroService.getSession(sessionId);
}
@Override
public void update(Session session) throws UnknownSessionException
{
super.update(session);
}
// EnterpriseCacheSessionDAO有一个方法:
// protected Serializable doCreate(Session session) {
// /*通过sessionId保存对应的session*/
// Serializable sessionId = this.generateSessionId(session);
// /*将sessionId和session捆绑*/
// this.assignSessionId(session, sessionId);
// return sessionId;
// }
/**
* 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
*/
public void syncToDb(OnlineSession onlineSession)
{
Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP);
if (lastSyncTimestamp != null)
{
boolean needSync = true;
long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime();
if (deltaTime < dbSyncPeriod * 60 * 1000)
{
// 时间差不足 无需同步
needSync = false;
}
// isGuest = true 访客
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
// session 数据变更了 同步
if (!isGuest && onlineSession.isAttributeChanged())
{
needSync = true;
}
if (!needSync)
{
return;
}
}
// 更新上次同步数据库时间
onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());
// 更新完后 重置标识
if (onlineSession.isAttributeChanged())
{
onlineSession.resetAttributeChanged();
}
AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession));
}
/**
* 当会话过期/停止(如用户退出时)属性等会调用
*/
@Override
protected void doDelete(Session session)
{
OnlineSession onlineSession = (OnlineSession) session;
if (null == onlineSession)
{
return;
}
onlineSession.setStatus(OnlineStatus.off_line);
sysShiroService.deleteSession(onlineSession);
}
}