天天看點

springboot + shiro之登入人數限制、登入判斷重定向、session時間設定springboot + shiro之登入人數控制

springboot + shiro之登入人數控制

項目

前篇:spring boot + mybatis + layui + shiro背景權限管理系統:http://blog.51cto.com/wyait/2082803

本文是基于spring boot + mybatis + layui + shiro背景權限管理系統開發的,新增功能:

  1. shiro并發登陸人數控制(超出登入使用者最大配置數量,清理使用者)功能;
  2. 解決在父子頁面中,判斷使用者未登入之後,重定向到登入頁面嵌套顯示問題;
  3. 解決ajax請求,判斷使用者未登入之後,如何重定向到登入頁面問題;
  4. 解決使用并完成了功能1,導緻的session有效時間沖突問題。

後篇:

  • springboot + shiro 動态更新使用者資訊:http://blog.51cto.com/wyait/2112200
  • springboot + shiro 權限注解、統一異常處理、請求亂碼解決 :http://blog.51cto.com/wyait/2125708

項目源碼

項目源碼:(包含資料庫源碼)

github源碼: https://github.com/wyait/manage.git

碼雲:https://gitee.com/wyait/manage.git

github對應項目源碼目錄:wyait-manage-1.2.0

碼雲對應項目源碼目錄:wyait-manage-1.2.0

場景

同一個使用者,先在A×××登入;之後在B×××登入時,退出A×××的登入狀态;反之相同。或者限制同一個使用者在不同的裝置上,同時線上的數量;

技術實作

基于shiro和ehcache實作

解決思路

spring security就直接提供了相應的功能;

Shiro的話沒有提供預設實作,不過可以在Shiro中加入這個功能。就是使用shiro強大的自定義通路控制攔截器:AccessControlFilter,內建這個接口後要實作下面這2個方法。

/**
     * Returns <code>true</code> if the request is allowed to proceed through the filter normally, or <code>false</code>
     * if the request should be handled by the
     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(request,response,mappedValue)}
     * method instead.
     *
     * @param request     the incoming <code>ServletRequest</code>
     * @param response    the outgoing <code>ServletResponse</code>
     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
     * @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
     *         request should be processed by this filter's
     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
     * @throws Exception if an error occurs during processing.
     */
    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;

    ... ...
    /**
     * Processes requests where the subject was denied access as determined by the
     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
     * method.
     *
     * @param request  the incoming <code>ServletRequest</code>
     * @param response the outgoing <code>ServletResponse</code>
     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
     *         handle/render the response directly.
     * @throws Exception if there is an error processing the request.
     */
    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
           

檢視抽象類AccessControlFilter:

  • isAccessAllowed:表示是否允許通路;mappedValue就是[urls]配置中攔截器參數部分,如果允許通路傳回true,否則false;
  • onAccessDenied:表示當通路拒絕時是否已經處理了;如果傳回true表示需要繼續處理;如果傳回false表示該攔截器執行個體已經處理了,将直接傳回即可。
  • onPreHandle:會自動調用這兩個方法決定是否繼續處理;

另外AccessControlFilter還提供了如下方法用于處理如登入成功後/重定向到上一個請求:

springboot + shiro之登入人數限制、登入判斷重定向、session時間設定springboot + shiro之登入人數控制
void setLoginUrl(String loginUrl) //身份驗證時使用,預設/login.jsp  
String getLoginUrl()  
Subject getSubject(ServletRequest request, ServletResponse response) //擷取Subject執行個體  
boolean isLoginRequest(ServletRequest request, ServletResponse response)//目前請求是否是登入請求  
void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将目前請求儲存起來并重定向到登入頁面  
void saveRequest(ServletRequest request) //将請求儲存起來,如登入成功後再重定向回該請求  
void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登入頁面
           

要進行使用者通路控制,可以繼承AccessControlFilter。

  • 思路:

    a. 登陸成功時将使用者儲存到了shiro提供的session中,并同時添加到ehcache緩存中;

    b. KickoutSessionFilter拿到了session之後先判斷能不能通過緩存取到值,如果取得到再和伺服器端session進行比對(使用者的名字(每個使用者的名字必須不同));

    c. 如果比對,系統會為新登入的使用者建立一個session;之前的session确認失效并踢出,老使用者就無法繼續操作而被迫下線;

shiro技術實作流程

下面就是自定義的通路控制攔截器:KickoutSessionFilter:

自定義過濾器類KickoutSessionFilter

package com.wyait.manage.filter;

import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import com.wyait.manage.pojo.User;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
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.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lyd.admin.pojo.AdminUser;

/**
 * 
 * @項目名稱:wyait-manager
 * @類名稱:KickoutSessionFilter
 * @類描述:自定義過濾器,進行使用者通路控制
 * @建立人:wyait
 * @建立時間:2018年4月24日 下午5:18:29
 * @version:
 */
public class KickoutSessionFilter extends AccessControlFilter {

    private static final Logger logger = LoggerFactory
            .getLogger(KickoutSessionFilter.class);

    private String kickoutUrl; // 踢出後到的位址
    private boolean kickoutAfter = false; // 踢出之前登入的/之後登入的使用者 預設false踢出之前登入的使用者
    private int maxSession = 1; // 同一個帳号最大會話數 預設1
    private SessionManager sessionManager;
    private 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;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    // 設定Cache的key的字首
    public void setCacheManager(CacheManager cacheManager) {
        //必須和ehcache緩存配置中的緩存name一緻
        this.cache = cacheManager.getCache("shiro-activeSessionCache");
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request,
            ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        // 沒有登入授權 且沒有記住我
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            // 如果沒有登入,直接進行之後的流程
            return true;
        }
        Session session = subject.getSession();
        logger.debug("==session時間設定:" + String.valueOf(session.getTimeout())
                + "===========");
        try {
            // 目前使用者
            User user = (User) subject.getPrincipal();
            String username = user.getUsername();
            logger.debug("===目前使用者username:==" + username);
            Serializable sessionId = session.getId();
            logger.debug("===目前使用者sessionId:==" + sessionId);
            // 讀取緩存使用者 沒有就存入
            Deque<Serializable> deque = cache.get(username);
            logger.debug("===目前deque:==" + deque);
            if (deque == null) {
                // 初始化隊列
                deque = new ArrayDeque<Serializable>();
            }
            // 如果隊列裡沒有此sessionId,且使用者沒有被踢出;放入隊列
            if (!deque.contains(sessionId)
                    && session.getAttribute("kickout") == null) {
                // 将sessionId存入隊列
                deque.push(sessionId);
                // 将使用者的sessionId隊列緩存
                cache.put(username, deque);
            }
            // 如果隊列裡的sessionId數超出最大會話數,開始踢人
            while (deque.size() > maxSession) {
                logger.debug("===deque隊列長度:==" + deque.size());
                Serializable kickoutSessionId = null;
                // 是否踢出後來登入的,預設是false;即後者登入的使用者踢出前者登入的使用者;
                if (kickoutAfter) { // 如果踢出後者
                    kickoutSessionId = deque.removeFirst();
                } else { // 否則踢出前者
                    kickoutSessionId = deque.removeLast();
                }
                // 踢出後再更新下緩存隊列
                cache.put(username, deque);
                try {
                    // 擷取被踢出的sessionId的session對象
                    Session kickoutSession = sessionManager
                            .getSession(new DefaultSessionKey(kickoutSessionId));
                    if (kickoutSession != null) {
                        // 設定會話的kickout屬性表示踢出了
                        kickoutSession.setAttribute("kickout", true);
                    }
                } catch (Exception e) {// ignore exception
                }
            }
            // ajax請求

            // 如果被踢出了,(前者或後者)直接退出,重定向到踢出後的位址
            if ((Boolean) session.getAttribute("kickout") != null
                    && (Boolean) session.getAttribute("kickout") == true) {
                // 會話被踢出了
                try {
                    // 登出
                    subject.logout();
                } catch (Exception e) { // ignore
                }
                saveRequest(request);
                logger.debug("==踢出後使用者重定向的路徑kickoutUrl:" + kickoutUrl);
                // 重定向
                WebUtils.issueRedirect(request, response, kickoutUrl);
                return false;
            }
            return true;
        } catch (Exception e) { // ignore
            //重定向到登入界面
            WebUtils.issueRedirect(request, response, "/login");
            return false;
        }
    }

}
           

設定ShiroConfig配置類

  • SessionDAO 用于會話的CRUD;檢視該接口源碼:
public interface SessionDAO {  
    /*如DefaultSessionManager在建立完session後會調用該方法; 
      如儲存到關系資料庫/檔案系統/NoSQL資料庫;即可以實作會話的持久化; 
      傳回會話ID;主要此處傳回的ID.equals(session.getId());   
    */    
    Serializable create(Session session);    

    //根據會話ID擷取會話    
    Session readSession(Serializable sessionId) throws UnknownSessionException;    

    //更新會話;如更新會話最後通路時間/停止會話/設定逾時時間/設定移除屬性等會調用    
    void update(Session session) throws UnknownSessionException;    

    //删除會話;當會話過期/會話停止(如使用者退出時)會調用    
    void delete(Session session);    

    //擷取目前所有活躍使用者,如果使用者量多此方法影響性能    
    Collection<Session> getActiveSessions();     
}
           

SessionDAO實作類:

a. AbstractSessionDAO提供了SessionDAO的基礎實作,如生成會話ID等;  
b. CachingSessionDAO提供了對開發者透明的會話緩存的功能,隻需要設定相應的CacheManager即可;  
c. MemorySessionDAO直接在記憶體中進行會話維護;  
d. EnterpriseCacheSessionDAO提供了緩存功能的會話維護,預設情況下使用MapCache實作,内部使用ConcurrentHashMap儲存緩存的會話。
           
  1. ShiroConfig配置類中EnterpriseCacheSessionDAO配置:
    /**
     * EnterpriseCacheSessionDAO shiro sessionDao層的實作;
     * 提供了緩存功能的會話維護,預設情況下使用MapCache實作,内部使用ConcurrentHashMap儲存緩存的會話。
     */
    @Bean
    public EnterpriseCacheSessionDAO enterCacheSessionDAO() {
        EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //添加緩存管理器
        //enterCacheSessionDAO.setCacheManager(ehCacheManager());
        //添加ehcache活躍緩存名稱(必須和ehcache緩存名稱一緻)
        enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return enterCacheSessionDAO;
    }
               
  2. SessionManager配置:
/**
     *
     * @描述:sessionManager添加session緩存操作DAO
     * @建立人:wyait
     * @建立時間:2018年4月24日 下午8:13:52
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //sessionManager.setCacheManager(ehCacheManager());
        sessionManager.setSessionDAO(enterCacheSessionDAO());
        return sessionManager;
    }
           
  1. kickoutSessionFilter配置
/**
     *
     * @描述:kickoutSessionFilter同一個使用者多裝置登入限制
     * @建立人:wyait
     * @建立時間:2018年4月24日 下午8:14:28
     * @return
     */
    public KickoutSessionFilter kickoutSessionFilter(){
        KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
        //使用cacheManager擷取相應的cache來緩存使用者登入的會話;用于儲存使用者—會話之間的關系的;
        //這裡我們還是用之前shiro使用的ehcache實作的cacheManager()緩存管理
        //也可以重新另寫一個,重新配置緩存時間之類的自定義緩存屬性
        kickoutSessionFilter.setCacheManager(ehCacheManager());
        //用于根據會話ID,擷取會話進行踢出操作的;
        kickoutSessionFilter.setSessionManager(sessionManager());
        //是否踢出後來登入的,預設是false;即後者登入的使用者踢出前者登入的使用者;踢出順序。
        kickoutSessionFilter.setKickoutAfter(false);
        //同一個使用者最大的會話數,預設1;比如2的意思是同一個使用者允許最多同時兩個人登入;
        kickoutSessionFilter.setMaxSession(1);
        //被踢出後重定向到的位址;
        kickoutSessionFilter.setKickoutUrl("/toLogin?kickout=1");
        return kickoutSessionFilter;
    }
           
  1. 将SessionManager交給SecurityManager管理
/**
     * shiro安全管理器設定realm認證、ehcache緩存管理、session管理器、Cookie記住我管理器
     * @return
     */
    @Bean public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設定realm.
        securityManager.setRealm(shiroRealm());
        // //注入ehcache緩存管理器;
        securityManager.setCacheManager(ehCacheManager());
        // //注入session管理器;
        securityManager.setSessionManager(sessionManager());
        //注入Cookie記住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }
           
  1. 配置filterChainDefinitionMap
...
//添加kickout認證
HashMap<String,Filter> hashMap=new HashMap<String,Filter>();
hashMap.put("kickout",kickoutSessionFilter());
shiroFilterFactoryBean.setFilters(hashMap);
...
filterChainDefinitionMap.put("/**", "kickout,authc");
...
           

解決子頁面,重定向之後,出現頁面嵌套的問題

新增登入中轉頁面toLogin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!--head部分-->
<head th:include="layout :: htmlhead" th:with="title='利易達貸款背景'">
</head>
<script type="text/javascript">
var href=window.location.href;
if(href.indexOf("kickout")>0){
    setTimeout("top.location.href='/login?kickout';", 0); 
}else{
    setTimeout("top.location.href='/login';", 0); 
}
</script>
</html>
           

更改shiro中filterChainDefinitionMap配置

// 指定要求登入時的連結
shiroFilterFactoryBean.setLoginUrl("/toLogin");
...
// 配置不會被攔截的連結 從上向下順序判斷
filterChainDefinitionMap.put("/login", "anon");
           

上面兩個配置,即可解決頁面重定向後,嵌套問題。

ajax請求問題

如果對使用者線上數量進行限制,踢出了之前登入的使用者A;這時候使用者A在系統中,發送了一個ajax請求,會出現彈框空白等問題;

解決方案

  1. 自定義ShiroFilterUtils工具類判斷請求是否為ajax
package com.wyait.manage.utils;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 
 * @項目名稱:wyait-manager
 * @類名稱:ShiroFilterUtils
 * @類描述:shiro工具類
 * @建立人:wyait
 * @建立時間:2018年4月24日 下午5:12:04 
 * @version:
 */
public class ShiroFilterUtils {
    private static final Logger logger = LoggerFactory
            .getLogger(ShiroFilterUtils.class);
    /**
     * 
     * @描述:判斷請求是否是ajax
     * @建立人:wyait
     * @建立時間:2018年4月24日 下午5:00:22
     * @param request
     * @return
     */
    public static boolean isAjax(ServletRequest request){
        String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
        if("XMLHttpRequest".equalsIgnoreCase(header)){
            logger.debug("shiro工具類【wyait-manager-->ShiroFilterUtils.isAjax】目前請求,為Ajax請求");
            return Boolean.TRUE;
        }
        logger.debug("shiro工具類【wyait-manager-->ShiroFilterUtils.isAjax】目前請求,非Ajax請求");
        return Boolean.FALSE;
    }
}
           
  1. 調整KickoutSessionFilter過濾器,新增ajax請求判斷和響應
private final static ObjectMapper objectMapper = new ObjectMapper();
...
// ajax請求
/**
 * 判斷是否已經踢出
 * 1.如果是Ajax 通路,那麼給予json傳回值提示。
 * 2.如果是普通請求,直接跳轉到登入頁
 */
//判斷是不是Ajax請求
ResponseResult responseResult = new ResponseResult();
if (ShiroFilterUtils.isAjax(request) ) {
    logger.debug(getClass().getName()+ "目前使用者已經在其他地方登入,并且是Ajax請求!");
    responseResult.setCode(IStatusMessage.SystemStatus.MANY_LOGINS.getCode());
    responseResult.setMessage("您已在别處登入,請您修改密碼或重新登入");
    out(response, responseResult);
}else{
    // 重定向
    WebUtils.issueRedirect(request, response, kickoutUrl);
}
...
/**
 * 
 * @描述:response輸出json
 * @建立人:wyait
 * @建立時間:2018年4月24日 下午5:14:22
 * @param response
 * @param result
 */
public static void out(ServletResponse response, ResponseResult result){
    PrintWriter out = null;
    try {
        response.setCharacterEncoding("UTF-8");//設定編碼
        response.setContentType("application/json");//設定傳回類型
        out = response.getWriter();
        out.println(objectMapper.writeValueAsString(result));//輸出
        logger.error("使用者線上數量限制【wyait-manager-->KickoutSessionFilter.out】響應json資訊成功");
    } catch (Exception e) {
        logger.error("使用者線上數量限制【wyait-manager-->KickoutSessionFilter.out】響應json資訊出錯", e);
    }finally{
        if(null != out){
            out.flush();
            out.close();
        }
    }
}
           
  1. 前端編寫公共判斷使用者是否登入方法isLogin
/**
 * 判斷是否登入,沒登入重新整理目前頁,促使Shiro攔截後跳轉登入頁
 * @param result    ajax請求傳回的值
 * @returns {如果沒登入,重新整理目前頁}
 */
function isLogin(result){
    if(result && result.code && result.code == '1101'){
        window.location.reload(true);//重新整理目前頁
    }
    return true;//傳回true
}
           
  1. js中ajax調用isLogin方法
$.post("/user/delUser",{"id":id},function(data){
    //判斷使用者是否登入
    if(isLogin(data)){
        if(data=="ok"){
            //回調彈框
            layer.alert("删除成功!",function(){
                layer.closeAll();
                //加載load方法
                load(obj);//自定義
            });
        }else{
            layer.alert(data);//彈出錯誤提示
        }
    }
});
           

隻改動了userList.js使用者清單界面,其他界面//TODO

  1. 測試

    同一個使用者線上沖突測試,然後點選先登入使用者界面中其中一個ajax方法,如果背景使用者已退出,前台isLogin重新整理頁面,重新請求重定向到/toLogin?kickout頁面,最終跳轉到登入界面。

session有效時間設定

session預設有效時間:30分鐘(1800s)

  • spring boot session時間配置:
    # 會話逾時(秒)1天
    server.session.timeout=86400
               
  • session有效時間問題

    使用shiro進行使用者線上數量限制功能中,securityManager配置sessionManager之後,springboot中配置的session有效時間無效(sessionManager管理器覆寫了springboot中session有效時間的配置)。

session過期問題

使用shiro進行使用者線上數量限制功能;使用者登入後,2分鐘不操作,之後session失效。

原因

  1. spring boot整合shiro,在使用shiro進行使用者線上數量限制時,重新配置了SessionManger,
// //注入session管理器;
securityManager.setSessionManager(sessionManager());
           

SessionManager,配置EnterpriseCacheSessionDAO:

sessionManager.setSessionDAO(enterCacheSessionDAO());
           

EnterpriseCacheSessionDAO類,存取session的時候,是通過ehcache緩存中操作的。

這裡如果配置有緩存的話需要給其配置一個cache的鍵類似于:

shiro預設了一個預設值為:shiro-activeSessionCache,如果不相同(cache檔案中的鍵值) 需要進行替換,最終進行session存取的類為CachingSessionDAO

緩存管理器使用的是org.apache.shiro.cache.ehcache.EhCacheManager,那麼最終shiro在找session的時候也會調用getCache。

Ehcache.xml配置

<!-- shiro-activeSessionCache活躍使用者session緩存政策 -->
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
    </cache>
           

這裡配置了session緩存時間為2分鐘,故會出現登入2分鐘無操作後,session失效問題。

  1. shiro拿到ehcache緩存中的session後,和伺服器中的session校驗比對,這時,如果伺服器的session失效,也會出現問題;

    假設設定伺服器端目前使用者的session為30s【

    SecurityUtils.getSubject().getSession().setTimeout(30000);//毫秒
               
    】,ehcache中session有效時間120s不變;在無操作30s後,請求背景,報錯如下:
org.apache.shiro.session.ExpiredSessionException: Session with id [8aac0daf-c432-44b6-86cc-a618095ad2bd] has expired. Last access time: 18-4-24 上午11:32.  Current time: 18-4-24 上午11:33.  Session timeout is set to 30 seconds (0 minutes)
    at org.apache.shiro.session.mgt.SimpleSession.validate(SimpleSession.java:292) ~[shiro-core-1.3.1.jar:1.3.1]
    at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doValidate(AbstractValidatingSessionManager.java:186) ~[shiro-core-1.3.1.jar:1.3.1]
... ...
           

故ehcache緩存中session的有效時間和伺服器端session有效時間必須配置一緻。

解決方案

  1. 服務端session時間設定:
//session有效時間1天(毫秒)
SecurityUtils.getSubject().getSession().setTimeout(86400000);
           
  • 設定的最大時間,正負都可以,為負數時表示永不逾時。
    SecurityUtils.getSubject().getSession().setTimeout(-1000l);
               
    注意:這裡設定的時間機關是:ms,但是Shiro會把這個時間轉成:s,而且是會舍掉小數部分,這樣設定的是-1ms,轉成s後就是0s,馬上就過期了。所有要是除以1000以後還是負數,必須設定小于-1000
  1. 将Ehcache.xml時間配置和伺服器設定的session有效時間保持一緻。
    <!-- shiro-activeSessionCache活躍使用者session緩存政策(秒) -->
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           timeToIdleSeconds="86400"
           timeToLiveSeconds="86400"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
    </cache>
               
    通過代碼中檢視session有效時間:
logger.debug("session設定的有效時間:"+request.getSession().getMaxInactiveInterval());
logger.debug("shiro中session設定的有效時間:"+SecurityUtils.getSubject().getSession().getTimeout());
//86400(秒)
//86400000(毫秒)
           

總結

具體實作可以根據具體需求做調整;近期提供redis實作版本。

20180426版本更新内容

  1. 編輯使用者自己成功後,執行退出,重新登入資訊生效;
  2. 禁止使用者删除自己;
  3. 優化使用者清單操作資訊提示;
  4. 角色管理清單,通過添加參數callback,實作菜單回顯選中;

20180503版本更新内容

  1. 新增使用者表version版本字段;
  2. 更新使用者操作,通過version字段來保證資料一緻;
  3. 新增通過攔截器實作動态更新使用者資訊(同步更新線上使用者資訊);
  4. 新增登入成功後預設頁面home.html;
  5. 頁面操作細節優化。

spring boot + shiro 動态更新使用者資訊

連結入口--> spring boot + shiro 動态更新使用者資訊:http://blog.51cto.com/wyait/2112200

20180606版本更新内容

  1. 新增shiro權限注解;
  2. 請求亂碼問題解決;
  3. 統一異常處理;
  4. 頁面操作細節優化。

springboot + shiro 權限注解、統一異常處理、請求亂碼解決

連結入口--> springboot + shiro 權限注解、統一異常處理、請求亂碼解決 :http://blog.51cto.com/wyait/2125708

TODO

  • 背景方法級别權限控制,通過shiro配置可實作;具體使用者管理的操作根據業務實際的需求可做調整;

以上更新,項目wyait-manage、wyait-manage-1.2.0源碼同步更新。

前篇:

spring boot + mybatis + layui + shiro背景權限管理系統:http://blog.51cto.com/wyait/2082803

後篇:

  • springboot + shiro 動态更新使用者資訊:http://blog.51cto.com/wyait/2112200
  • springboot + shiro 權限注解、統一異常處理、請求亂碼解決 :http://blog.51cto.com/wyait/2125708

項目源碼:(包含資料庫源碼)

github源碼: https://github.com/wyait/manage.git

碼雲:https://gitee.com/wyait/manage.git

github對應項目源碼目錄:wyait-manage-1.2.0

碼雲對應項目源碼目錄:wyait-manage-1.2.0

繼續閱讀