1、什麼是rememberMe
shiro爲我們提供了rememberMe功能,也就是記住我功能,這個功能的作用很簡單,就是記住我,很多網站上都會有15天免登陸之類的功能,當我們設置了rememberMe功能後,只要登錄一次,在不手動退出登錄的情況下直接關閉瀏覽器,然後再次打開瀏覽器,仍然可以直接訪問到屬於自己的信息,這裏就實現了記住我功能。
2、rememberMe工作原理
- 我們打開瀏覽器,進入我們的權限驗證頁面,可以看到session信息
- 在我們勾選了記住我後,再次登陸,如果登錄成功,則會在此位置生成記住我的一個cooike,shiro會記住我們這個識別碼,用作登陸過用戶的標記來實現user攔截器
- 我們查看shiro登錄的部分源碼,可以找到在AbstractRememberMeManager類的287行,部分判斷記住我源碼
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject);
//如果記住我是true,會執行下面這段代碼
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}
繼續深入查看,可以查看到最終調用了CookieRememberMeManager類中部分代碼實現了寫入cookie
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
3、使用rememberMe
- 在登錄接口上設置setRememberMe(true),我們在頁面上已經設置好,勾選選項即可
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (rememberMe != null) {
token.setRememberMe(rememberMe);
}
try {
// 登錄
subject.login(token);
- 在ShiroController新增/user接口
@RequestMapping("/user")
public ReturnMap user() {
return new ReturnMap().success().data("user可以訪問");
}
- 在shiro的 shirFilter方法中新增user攔截器,
注意要寫在 “/**”, "authc"前面
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 攔截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不會被攔截的鏈接 順序判斷
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 配置退出 過濾器,其中具體的退出代碼Shiro已經替我們實現了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/user", "user");
// 因爲目前演示頁面依附在此項目下,特爲演示頁面新增可無權限訪問,前後端分離後無需此設置
filterChainDefinitionMap.put("/login.html", "anon");
// <!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最爲下邊 -->因爲保存在LinkedHashMap中,順序很重要
// <!-- authc:所有url都必須認證通過纔可以訪問; anon:所有url都都可以匿名訪問-->
filterChainDefinitionMap.put("/**", "authc");// 設置/** 爲user後,記住我纔會生效
// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面,前後端分離設置此爲controller返回的未登錄的接口
// --------------------------------------------------
// 前後端分離使用下面設置
// shiroFilterFactoryBean.setLoginUrl("/login.html");
shiroFilterFactoryBean.setLoginUrl("/unauthorized");// 前後端分離只需要把需要登錄返回告訴前端頁面即可
// ---------------------------------------------------
// 登錄成功後跳轉的鏈接,前後端分離不用設置
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授權的界面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
- 頁面新增user接口按鈕
- 進行測試
- 未登錄狀態直接點擊user按鈕(無法訪問)
- 未勾選記住我,登錄成功後訪問user按鈕(可以訪問)
- 關閉瀏覽器後再打開,點擊訪問user(無權限,並且sessionid變成全新的)
- 勾選記住我登錄,可以看到user接口可以訪問,同時cookie下出現了rememberMe的cookie,有效期一年,默認httponly
- 重新打開瀏覽器,直接點擊訪問user接口(可以訪問,在這可以看到,rememberMe一直存在,sessionid變成全新的了)
- 這裏需要注意的是,即使你上次登錄的是guest用戶,選擇了記住我功能,但是仍然不能使用rememberMe功能實現guest接口的訪問,這也是shiro故意控制的一點
3 、使用自定義rememberMe
- 在ShiroConfig類中添加cookie對象
/**
* Cookie 對象 用戶免登陸操作,但是需要配置filter /** 權限爲user生效
*
* @return
*/
public SimpleCookie rememMeCookie() {
// 初始化設置cookie的名稱
SimpleCookie simpleCookie = new SimpleCookie("boot-shiro");
simpleCookie.setMaxAge(2592000);// 設置cookie的生效時間
simpleCookie.setHttpOnly(true);
return simpleCookie;
}
- 添加cookie管理器bean
/**
* cookie 管理對象,記住我功能
*
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememMeCookie());
// remeberMe cookie 加密的密鑰 各個項目不一樣 默認AES算法 密鑰長度(128 256 512)
cookieRememberMeManager.setCipherKey(Base64.decode(ENCRYPTION_KEY));
return cookieRememberMeManager;
}
- 把cookie管理器交給SecurityManager
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setRememberMeManager(rememberMeManager());//把cookie管理器交給SecurityManager
return securityManager;
}
4、總結
- shiro的基礎篇到此就結束了,後續會更新shiro使用ehcache和redis的總結以及遇到的問題的篇幅
- 需要源碼可以點擊這裏獲取!