SpringBoot+Shiro定義攔截器管理線上使用者
自定義訪問控制攔截器:AccessControlFilter,整合這個介面後要實現下面這三個方法。
abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
在ShiroConfig
/** * 限制同一賬號登入同時登入人數控制 * @return */ //@Bean public GunsUserFilter gunsUserFilter(){ GunsUserFilter gunsUserFilter = new GunsUserFilter(); //使用cacheManager獲取相應的cache來快取使用者登入的會話;用於儲存使用者—會話之間的關係的; //這裡我們還是用之前shiro使用的redisManager()實現的cacheManager()快取管理 //也可以重新另寫一個,重新配置快取時間之類的自定義快取屬性 gunsUserFilter.setCacheManager(getCacheShiroManager(new EhCacheManagerFactoryBean())); gunsUserFilter.setKickoutAfter(false); //同一個使用者最大的會話數,預設1;比如2的意思是同一個使用者允許最多同時兩個人登入; gunsUserFilter.setMaxSession(1); //被踢出後重定向到的地址; gunsUserFilter.setKickoutUrl("/login"); return gunsUserFilter; } 自定義GunsUserFilter
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.stylefeng.guns.core.intercept; import com.stylefeng.guns.config.web.ShiroConfig; import com.stylefeng.guns.core.cache.CacheKit; import com.stylefeng.guns.core.shiro.ShiroKit; import com.stylefeng.guns.core.shiro.ShiroUser; import com.stylefeng.guns.core.common.constant.cache.Cache; import com.stylefeng.guns.core.util.SpringContextHolder; import com.stylefeng.guns.core.util.ToolUtil; import org.apache.shiro.SecurityUtils; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionException; import org.apache.shiro.session.mgt.DefaultSessionKey; import org.apache.shiro.session.mgt.SessionContext; import org.apache.shiro.session.mgt.SessionKey; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import javax.annotation.Resource; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.Serializable; import java.util.Deque; import java.util.LinkedList; /** * Filter that allows access to resources if the accessor is a known user, which is defined as * having a known principal. This means that any user who is authenticated or remembered via a * 'remember me' feature will be allowed access from this filter. * <p/> * If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}</p> * * @since 0.9 */ public class GunsUserFilter extends AccessControlFilter { private String kickoutUrl; //踢出後到的地址 private boolean kickoutAfter = false; //踢出之前登入的/之後登入的使用者 預設踢出之前登入的使用者 private int maxSession = 1; //同一個帳號最大會話數 預設1 private org.apache.shiro.cache.Cache<String, Deque<Serializable>> cache; public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } //設定Cache的key的字首 public void setCacheManager(CacheManager cacheManager) { this.cache = cacheManager.getCache("shiro_redis_cache"); } /** * Returns <code>true</code> if the request is a * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject} * is not <code>null</code>, <code>false</code> otherwise. * * @return <code>true</code> if the request is a * {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or * if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject} * is not <code>null</code>, <code>false</code> otherwise. */ protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; /*if (isLoginRequest(request, response)) { return true; } else { Subject subject = getSubject(request, response); // If principal is not null, then the user is known and should be allowed access. return subject.getPrincipal() != null; }*/ } /** * This default implementation simply calls * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) saveRequestAndRedirectToLogin} * and then immediately returns <code>false</code>, thereby preventing the chain from continuing so the redirect may * execute. */ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); HttpServletResponse httpServletResponse = WebUtils.toHttp(response); /** * 如果是ajax請求則不進行跳轉 */ if (httpServletRequest.getHeader("x-requested-with") != null && httpServletRequest.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) { httpServletResponse.setHeader("sessionstatus", "timeout"); return false; } else { /*-----------------單機使用者唯一登陸------------------------------*/ /** * * @date 建立時間:2018年3月27日 * 1.讀取當前登入使用者名稱,獲取在快取中的sessionId佇列 * 2.判斷佇列的長度,大於最大登入限制的時候,按踢出規則 * 將之前的sessionId中的session域中存入kickout:true,並更新佇列快取 * 3.判斷當前登入的session域中的kickout如果為true, * 想將其做退出登入處理,然後再重定向到踢出登入提示頁面 */ Subject subject = getSubject(request, response); if(!subject.isAuthenticated() && !subject.isRemembered()) { //如果沒有登入,直接進行之後的流程 return true; } Session session = subject.getSession(); ShiroUser user = ShiroKit.getUser(); String username = user.getAccount(); Serializable sessionId = session.getId(); //讀取快取 沒有就存入 Deque<Serializable> deque = cache.get(username); //如果此使用者沒有session佇列,也就是還沒有登入過,快取中沒有 //就new一個空佇列,不然deque物件為空,會報空指標 if(deque==null){ deque = new LinkedList<Serializable>(); } //如果佇列裡沒有此sessionId,且使用者沒有被踢出;放入佇列 if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) { //將sessionId存入佇列 deque.push(sessionId); //將使用者的sessionId佇列快取 cache.put(username, deque); } //如果佇列裡的sessionId數超出最大會話數,開始踢人 while(deque.size() > maxSession) { Serializable kickoutSessionId = null; if(kickoutAfter) { //如果踢出後者 kickoutSessionId = deque.removeFirst(); //踢出後再更新下快取佇列 cache.put(username, deque); } else { //否則踢出前者 kickoutSessionId = deque.removeLast(); //踢出後再更新下快取佇列 cache.put(username, deque); } try { //獲取被踢出的sessionId的session物件 Session kickoutSession = SecurityUtils.getSecurityManager().getSession(new DefaultSessionKey(kickoutSessionId)); if(kickoutSession != null) { //設定會話的kickout屬性表示踢出了 kickoutSession.setAttribute("kickout", true); } } catch (Exception e) {//ignore exception } } //如果被踢出了,直接退出,重定向到踢出後的地址 if ((Boolean)session.getAttribute("kickout")!=null&&(Boolean)session.getAttribute("kickout") == true) { System.out.println("被踢出"); //會話被踢出了 try { //退出登入 subject.logout(); } catch (Exception e) { //ignore } saveRequest(request); //重定向 WebUtils.issueRedirect(request, response, kickoutUrl); return false; } return true; /*--------------------------------------------------------*/ /** * 第一次點選頁面 */ /*String referer = httpServletRequest.getHeader("Referer"); if (referer == null) { System.out.println("進來7"); redirectToLogin(request, response); return false; } else { System.out.println("進來8"); //從別的頁面跳轉過來的 if (ShiroKit.getSession().getAttribute("sessionFlag") == null) { System.out.println("進來9"); httpServletRequest.setAttribute("tips", "session超時"); httpServletRequest.getRequestDispatcher("/login.html").forward(request, response); return false; } else { System.out.println("進來10"); redirectToLogin(request, response); return false; } }*/ } } }