shiro中提供了rememberMe功能,它用起來是這樣的html
[java] view plaincopyjava
UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(),loginForm.getPassword()); web
if(loginForm.getRememberMe() != null && "Y".equals(loginForm.getRememberMe())){ spring
token.setRememberMe(true); 數據庫
} apache
你能夠本身設置一個標誌位,而後根據這個標誌位判斷一下用戶是否勾選了記住我,若是勾選了就使用 token.setRememberMe(true) 設置爲記住我。瀏覽器
相信不少人跟我一開始想的同樣,以爲這樣設置完了,而後不退出直接關瀏覽器再打開瀏覽器,進入咱們的網站就會自動登錄。可是結果是:當你重開了瀏覽器後,進入網站依然讓你輸入用戶名和密碼!安全
那麼,究竟這個功能要怎麼使用呢?cookie
其實你設置了這個rememberMe以後shiro仍是有作一點事情的,它會生成一個cookie值叫 rememberMe 並保存在你的瀏覽器裏面,並且這個參數會隨着你調用 subject.logout() 會被自動清除。這個參數的值是一串很長的Base64加密過的字符串,大概長這樣session
[plain] view plaincopy
名稱: 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 = true 也不能算是 authc 的而是 user 級別的。
咱們通常設置路徑攔截是這樣設置的
[plain] view plaincopy
/** = authc
這樣就保證了全部路徑都須要登錄才能訪問。就算你是 rememberMe=true也不能訪問,官方說你若是設置成攔截級別爲user就能訪問,好比
[html] view plaincopy
/** = user
這樣就能夠訪問了,可是官方建議不敏感的部分用user,敏感的部分仍是要讓用戶再登錄一次,就像你上淘寶網就算不登錄,只要上一次有登錄過,你依然能夠直接看個人淘寶那個頁面,可是點擊 個人寶貝的時候就又要讓你登錄了。
可是!咱們的確有不少時候是須要記住用戶就至關於用戶登陸了!
設置成user這個方案還有一個問題,就是咱們實際項目中在登錄後有作了不少設置用戶上下文的工做,好比設置session等,若是咱們只是設置攔截級別爲user,那麼再次進入的時候雖然能夠訪問,可是session是空的,咱們的頁面必然異常頻出。
採用這個解決方案的前提是,你必須本身先實現一個realm,不過這個我相信你們都會實現的,畢竟默認的不是jdbcRealm ,真正的項目都是要查數據庫才能肯定用戶是否登陸的。那麼我就假定你們的項目中都有那麼一個負責驗證登陸的 JdbcRealm, 而且是採用用戶名密碼認證的,在 doGetAuthenticationInfo 方法裏面是採用以下的方法來作認證
[java] view plaincopy
...
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
這個前提條件保證你的principal是username,相信大部分人根據教程作shiro的時候都採用了這種方式
作一個新類繼承FormAuthenticationFilter ,並複寫 isAccessAllowed 方法
[java] view plaincopy
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();
}
}
若是你用的是spring那麼
[html] view plaincopy
<!-- 整合了rememberMe功能的filter -->
<bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>
<!--將以前的 /** = authc 替換成 rememberAuthFilter
...
/** = rememberAuthFilter
...
若是你用的是 ini 文件,那麼
[plain] view plaincopy
rememberAuthFilter=com.yqr.jxc.shiro.RememberAuthenticationFilter
#將以前的 /** = authc 替換成 rememberAuthFilter
...
/** = rememberAuthFilter
而後重啓項目咱們來測試一下,先登陸一次系統,而後直接關掉瀏覽器,而後打開瀏覽器直接輸入系統某個頁面的地址,發現能夠直接進去了,session什麼的也設置好了
忙活了半天,最後我仍是決定在個人系統中撤下了這個功能。爲何呢?由於這個功能有個致命的安全缺陷就是隨便誰把這個cookie值拿到別的瀏覽器均可以登陸。就算你用再牛逼的加密,或者是這個cookie值根據瀏覽器的各個別的屬性來達到僅供這個瀏覽器使用,可是對於黑客來講,只要你是經過表單把東西發送出去,這整個表單都是能夠僞造的。就算是增長了過時時間,在這段時間以內仍是有被僞造的風險,我目前沒有想到什麼好的解決方案。
惟一能想到的就是對於使用場景的選擇,在嚴格的業務系統中不能使用記住我這個功能,在非嚴格的系統中,好比不敏感的系統,像看看流量看看微博之類的,仍是可使用以上的方式來解決rememberMe的問題的。
因此,請謹慎選擇是否要將 rememberMe 功能範圍擴大化!
最後感謝來自俄羅斯的 meri 的這篇精闢的shiro研究文 http://meri-stuff.blogspot.com/2011/03/apache-shiro-part-1-basics.html 本文是根據meri 和 blurblurNick 精彩的問答寫成的