Shiro的 rememberMe 功能使用指導(為什么rememberMe設置了沒作用?)


原理解釋

shiro對cookie做了什么?

其實你設置了這個rememberMe之后shiro還是有做一點事情的,它會生成一個cookie值叫 rememberMe 並保存在你的瀏覽器里面,而且這個參數會隨着你調用 subject.logout() 會被自動清除。這個參數的值是一串很長的Base64加密過的字符串,大概長這樣
名稱:	rememberMe
內容:	6gYvaCGZaDXt1c0xwriXj/Uvz6g8OMT3VSaAK4WL0Fvqvkcm0nf3CfTwkWWTT4EjeSS/EoQjRfCPv4WKUXezQDvoNwVgFMtsLIeYMAfTd17ey5BrZQMxW+xU1lBSDoEM1yOy/i11ENh6eXjmYeQFv0yGbhchGdJWzk5W3MxJjv2SljlW4dkGxOSsol3mucoShzmcQ4VqiDjTcbVfZ7mxSHF/0M1JnXRphi8meDaIm9IwM4Hilgjmai+yzdVHFVDDHv/vsU/fZmjb+2tJnBiZ+jrDhl2Elt4qBDKxUKT05cDtXaUZWYQmP1bet2EqTfE8eiofa1+FO3iSTJmEocRLDLPWKSJ26bUWA8wUl/QdpH07Ymq1W0ho8EIdFhOsELxM66oMcj7a/8LVzypJXAXZdMFaNe8cBSN2dXpv4PwiktCs3J9P9vP4XrmYees5x27UmXNqYFk86xQhRjFdJsw5A9ctDKXzPYvJmWFouo3qT5hugX0uxWALCfWg8MHJnG9w7QgVKM8oy3Xy4Ut8lSvYlA==

  

這串字符串其實是對你登陸后的 Principal 進行了序列化后再Base64的結果。Principal 是 shiro 的一個概念,表示一個唯一的字符串能表示你這個用戶的,如果你按照最簡單的用戶名密碼登陸的方式,並且使用的是 SimpleAuthenticationInfo 對象,那么這個 Principal 其實就是一個字符串,就是你的用戶名 username
所以這串東西解密出來就是你的username
 

shiro覺得rememberMe不安全

shiro覺得不能把rememberMe等同於已經登陸了,這樣不安全。所以shiro 覺得就算 rememberMe = true 也不能算是 authc 的而是 user 級別的。
我們一般設置路徑攔截是這樣設置的
/** = authc

這樣就保證了所有路徑都需要登陸才能訪問。就算你是 rememberMe=true也不能訪問,官方說你如果設置成攔截級別為user就能訪問,比如
/** = user


這樣就可以訪問了,但是官方建議不敏感的部分用user,敏感的部分還是要讓用戶再登陸一次,就像你上淘寶網就算不登陸,只要上一次有登陸過,你依然可以直接看我的淘寶那個頁面,但是點擊 我的寶貝的時候就又要讓你登陸了。
但是!我們的確有很多時候是 需要記住用戶就相當於用戶登錄了!
設置成user這個方案還有一個問題,就是我們實際項目中在登陸后有做了很多設置用戶上下文的工作,比如設置session等,如果我們只是設置攔截級別為user,那么再次進入的時候雖然可以訪問,但是session是空的,我們的頁面必然異常頻出。
 

解決方案

前提條件

采用這個解決方案的前提是,你必須自己先實現一個realm,不過這個我相信大家都會實現的,畢竟默認的不是jdbcRealm ,真正的項目都是要查數據庫才能確定用戶是否登錄的。那么我就假定大家的項目中都有那么一個負責驗證登錄的 JdbcRealm, 並且是采用用戶名密碼認證的,在 doGetAuthenticationInfo 方法里面是采用如下的方法來做認證
 
...
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());

  

這個前提條件保證你的principal是username,相信大部分人根據教程做shiro的時候都采用了這種方式

STEP1  復寫 FormAuthenticationFilter 的 isAccessAllowed 方法

做一個新類繼承FormAuthenticationFilter ,並復寫 isAccessAllowed  方法
 
package com.yqr.jxc.shiro;
 
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
 
import com.yqr.jxc.service.global.GlobalUserService;
 
public class RememberAuthenticationFilter extends FormAuthenticationFilter {
	
	@Resource(name="globalUserService")
	private GlobalUserService globalUserService;
	
        /**
        * 這個方法決定了是否能讓用戶登錄
        */
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
 
        //如果 isAuthenticated 為 false 證明不是登錄過的,同時 isRememberd 為true 證明是沒登陸直接通過記住我功能進來的
        if(!subject.isAuthenticated() && subject.isRemembered()){
 
                //獲取session看看是不是空的
        	Session session = subject.getSession(true);
                
                //隨便拿session的一個屬性來看session當前是否是空的,我用userId,你們的項目可以自行發揮
        	if(session.getAttribute("userId") == null){
 
        		//如果是空的才初始化,否則每次都要初始化,項目得慢死
                        //這邊根據前面的前提假設,拿到的是username
        		String username = subject.getPrincipal().toString();
        		
        		//在這個方法里面做初始化用戶上下文的事情,比如通過查詢數據庫來設置session值,你們自己發揮
        		globalUserService.initUserContext(username, subject);
        	}
        }
 
        //這個方法本來只返回 subject.isAuthenticated() 現在我們加上 subject.isRemembered() 讓它同時也兼容remember這種情況
        return subject.isAuthenticated() || subject.isRemembered();
    }
}

  

STEP2 設置使用這個新的 AuthenticationFilter (認證過濾器)

如果你用的是spring那么
<!-- 整合了rememberMe功能的filter -->
<bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>
 
<!--將之前的 /** = authc 替換成 rememberAuthFilter
...
/** = rememberAuthFilter
...

  如果你用的是 ini 文件,那么

rememberAuthFilter=com.yqr.jxc.shiro.RememberAuthenticationFilter

#將之前的 /** = authc 替換成 rememberAuthFilter
...
/** = rememberAuthFilter

  

然后重啟項目我們來測試一下,先登錄一次系統,然后直接關掉瀏覽器,然后打開瀏覽器直接輸入系統某個頁面的地址,發現可以直接進去了,session什么的也設置好了
 

看起來很美?但是!

忙活了半天,最后我還是決定在我的系統中撤下了這個功能。為什么呢?因為這個功能有個致命的安全缺陷就是隨便誰把這個cookie值拿到別的瀏覽器都可以登錄。就算你用再牛逼的加密,或者是這個cookie值根據瀏覽器的各個別的屬性來達到僅供這個瀏覽器使用,但是對於黑客來說,只要你是通過表單把東西發送出去,這整個表單都是可以偽造的。就算是增加了過期時間,在這段時間之內還是有被偽造的風險,我目前沒有想到什么好的解決方案。
唯一能想到的就是對於使用場景的選擇,在嚴格的業務系統中不能使用記住我這個功能,在非嚴格的系統中,比如不敏感的系統,像看看流量看看微博之類的,還是可以使用以上的方式來解決rememberMe的問題的。
所以,請謹慎選擇是否要將 rememberMe 功能范圍擴大化!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM