Shiro的 rememberMe 功能使用指導(爲何rememberMe設置了沒做用?)

問題

shiro中提供了rememberMe功能,它用起來是這樣的html

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片java

  1. UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(),loginForm.getPassword());  web

  2.           

  3.         if(loginForm.getRememberMe() != null && "Y".equals(loginForm.getRememberMe())){  spring

  4.             token.setRememberMe(true);  數據庫

  5.         }  apache


你能夠本身設置一個標誌位,而後根據這個標誌位判斷一下用戶是否勾選了記住我,若是勾選了就使用 token.setRememberMe(true) 設置爲記住我。瀏覽器

相信不少人跟我一開始想的同樣,以爲這樣設置完了,而後不退出直接關瀏覽器再打開瀏覽器,進入咱們的網站就會自動登錄。可是結果是:當你重開了瀏覽器後,進入網站依然讓你輸入用戶名和密碼!安全

那麼,究竟這個功能要怎麼使用呢?cookie

原理解釋

shiro對cookie作了什麼?

其實你設置了這個rememberMe以後shiro仍是有作一點事情的,它會生成一個cookie值叫 rememberMe 並保存在你的瀏覽器裏面,並且這個參數會隨着你調用 subject.logout() 會被自動清除。這個參數的值是一串很長的Base64加密過的字符串,大概長這樣session

[plain] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. 名稱: rememberMe  

  2. 內容: 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 級別的。

咱們通常設置路徑攔截是這樣設置的

[plain] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. /** = authc  


這樣就保證了全部路徑都須要登錄才能訪問。就算你是 rememberMe=true也不能訪問,官方說你若是設置成攔截級別爲user就能訪問,好比

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. /** = user  


這樣就能夠訪問了,可是官方建議不敏感的部分用user,敏感的部分仍是要讓用戶再登錄一次,就像你上淘寶網就算不登錄,只要上一次有登錄過,你依然能夠直接看個人淘寶那個頁面,可是點擊 個人寶貝的時候就又要讓你登錄了。

可是!咱們的確有不少時候是須要記住用戶就至關於用戶登陸了!

設置成user這個方案還有一個問題,就是咱們實際項目中在登錄後有作了不少設置用戶上下文的工做,好比設置session等,若是咱們只是設置攔截級別爲user,那麼再次進入的時候雖然能夠訪問,可是session是空的,咱們的頁面必然異常頻出。

解決方案

前提條件

採用這個解決方案的前提是,你必須本身先實現一個realm,不過這個我相信你們都會實現的,畢竟默認的不是jdbcRealm ,真正的項目都是要查數據庫才能肯定用戶是否登陸的。那麼我就假定你們的項目中都有那麼一個負責驗證登陸的 JdbcRealm, 而且是採用用戶名密碼認證的,在 doGetAuthenticationInfo 方法裏面是採用以下的方法來作認證

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. ...  

  2. info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());  


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

STEP1  複寫 FormAuthenticationFilter 的 isAccessAllowed 方法

作一個新類繼承FormAuthenticationFilter ,並複寫 isAccessAllowed  方法

[java] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. package com.yqr.jxc.shiro;  

  2.   

  3. import javax.annotation.Resource;  

  4. import javax.servlet.ServletRequest;  

  5. import javax.servlet.ServletResponse;  

  6.   

  7. import org.apache.shiro.session.Session;  

  8. import org.apache.shiro.subject.Subject;  

  9. import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;  

  10.   

  11. import com.yqr.jxc.service.global.GlobalUserService;  

  12.   

  13. public class RememberAuthenticationFilter extends FormAuthenticationFilter {  

  14.       

  15.     @Resource (name="globalUserService")  

  16.     private GlobalUserService globalUserService;  

  17.       

  18.         /** 

  19.         * 這個方法決定了是否能讓用戶登陸 

  20.         */  

  21.     @Override  

  22.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  

  23.         Subject subject = getSubject(request, response);  

  24.   

  25.         //若是 isAuthenticated 爲 false 證實不是登陸過的,同時 isRememberd 爲true 證實是沒登錄直接經過記住我功能進來的  

  26.         if(!subject.isAuthenticated() && subject.isRemembered()){  

  27.   

  28.                 //獲取session看看是否是空的  

  29.             Session session = subject.getSession(true);  

  30.                   

  31.                 //隨便拿session的一個屬性來看session當前是不是空的,我用userId,大家的項目能夠自行發揮  

  32.             if(session.getAttribute("userId") == null){  

  33.   

  34.                 //若是是空的才初始化,不然每次都要初始化,項目得慢死  

  35.                         //這邊根據前面的前提假設,拿到的是username  

  36.                 String username = subject.getPrincipal().toString();  

  37.                   

  38.                 //在這個方法裏面作初始化用戶上下文的事情,好比經過查詢數據庫來設置session值,大家本身發揮  

  39.                 globalUserService.initUserContext(username, subject);  

  40.             }  

  41.         }  

  42.   

  43.         //這個方法原本只返回 subject.isAuthenticated() 如今咱們加上 subject.isRemembered() 讓它同時也兼容remember這種狀況  

  44.         return subject.isAuthenticated() || subject.isRemembered();  

  45.     }  

  46. }  

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

若是你用的是spring那麼

[html] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. <!-- 整合了rememberMe功能的filter -->  

  2. <bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>  

  3.   

  4. <!--將以前的 /** = authc 替換成 rememberAuthFilter  

  5. ...  

  6. /** = rememberAuthFilter  

  7. ...  


若是你用的是 ini 文件,那麼

[plain] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. rememberAuthFilter=com.yqr.jxc.shiro.RememberAuthenticationFilter  

  2.   

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

  4. ...  

  5. /** = rememberAuthFilter  


而後重啓項目咱們來測試一下,先登陸一次系統,而後直接關掉瀏覽器,而後打開瀏覽器直接輸入系統某個頁面的地址,發現能夠直接進去了,session什麼的也設置好了

看起來很美?可是!

忙活了半天,最後我仍是決定在個人系統中撤下了這個功能。爲何呢?由於這個功能有個致命的安全缺陷就是隨便誰把這個cookie值拿到別的瀏覽器均可以登陸。就算你用再牛逼的加密,或者是這個cookie值根據瀏覽器的各個別的屬性來達到僅供這個瀏覽器使用,可是對於黑客來講,只要你是經過表單把東西發送出去,這整個表單都是能夠僞造的。就算是增長了過時時間,在這段時間以內仍是有被僞造的風險,我目前沒有想到什麼好的解決方案。

惟一能想到的就是對於使用場景的選擇,在嚴格的業務系統中不能使用記住我這個功能,在非嚴格的系統中,好比不敏感的系統,像看看流量看看微博之類的,仍是可使用以上的方式來解決rememberMe的問題的。

因此,請謹慎選擇是否要將 rememberMe 功能範圍擴大化!

最後感謝來自俄羅斯的 meri 的這篇精闢的shiro研究文 http://meri-stuff.blogspot.com/2011/03/apache-shiro-part-1-basics.html 本文是根據meri 和 blurblurNick 精彩的問答寫成的

相關文章
相關標籤/搜索