開心一刻html
小明的朋友骨折了,小明去他家裏看他。他老婆很細心的爲他換藥,敷藥,而後出去買菜。小明滿臉羨慕地說:你特麼真幸福啊,你老婆對你那麼好!朋友哭得稀里嘩啦的說:兄弟你別說了,我幸福個錘子,就是她把我打骨折的。git
揣摩下此刻男人的心裏github
路漫漫其修遠兮,吾將上下而求索!redis
github:https://github.com/youzhibingspring
碼雲(gitee):https://gitee.com/youzhibing數據庫
上篇中主要講了兩點認證與受權,認證主要FormAuthenticationFilter和AnonymousFilter兩個filter來控制,shiro對全部請求都會先生成ProxiedFilterChain,請求會通過ProxiedFilterChain,先執行shiro的filter鏈,再執行剩下的servlet Filter鏈,最後來到咱們的Controller。緩存
認證過程是經過filter控制實現的,咱們全部的請求由shiro中3個Filter:LogoutFilter、AnonymousFilter、FormAuthenticationFilter分攤了,LogoutFilter負責/logout,AnonymousFilter負責/login和靜態資源,FormAuthenticationFilter則負責剩下的(/**),三個filter只會有一個生效(注意filter的配置順序);當FormAuthenticationFilter生效的時候會進行登陸認證,認證過程:先從緩存獲取authenticationInfo,沒有則經過realm從數據庫獲取並放入緩存,而後將頁面輸入的用戶信息(UsernamePasswordToken)與authenticationInfo進行匹配驗證,認證經過會將subject中的authenticated設置成true,表示當前subject已經被認證過了。關於認證緩存,我的不建議開啓,由於當修改用戶信息後,很差處理緩存中的authenticationInfo,另外認證頻率原本就不高,緩存的意義不大。session
通常狀況下受權是經過註解方式實現的,註解配合aop會在咱們的業務方法前織入前置權限檢查處理,檢查過程與認證過程相似:從緩存中獲取authorizationInfo,沒有則經過realm從數據庫獲取,而後放入緩存,而後將authorizationInfo與@RequiresPermissions("xxx")中的xxx來進行匹配,完成權限檢查,檢查經過則進入咱們的目標方法,不經過則拋出異常。關於權限緩存,我的建議開啓,由於權限的驗證仍是挺頻繁的,若是不開啓緩存,那麼會給數據庫形成必定的壓力。app
上篇遺留問題:session過時後,咱們再請求,shiro是如何處理並跳轉到登陸頁的?回答這個問題以前,咱們先看看另一個問題:ui
上篇博文中講到了登陸認證成功後會將subject的authenticated設置成true,表示當前subject已經被認證過了,可是隻是當前subject; 咱們能夠將subject當作是request,每次請求來的時候都會將request/response對封裝成subject,AbstractShiroFilter的方法doFilterInternal中有這樣一個調用 final Subject subject = createSubject(request, response); 咱們來看看createSubject的方法描述: Creates a WebSubject instance to associate with the incoming request/response pair which will be used throughout the request/response execution. 建立一個關聯request/response對的WebSubject實例,用於後續request/response的執行
也就是目前咱們還只是看到了當前請求有認證狀態,當前會話尚未看到認證狀態;撇開shiro,若是是咱們本身實現,咱們會怎麼實現,確定會在subject的authenticated設置成true的時候也將認證狀態也設置在session中,至因而存儲在自定義session的某個標誌字段(相似subject的authenticated)中,仍是存儲在session的attributes中(setAttribute(Object key, Object value)進行設置),看咱們的需求和我的喜愛。
概括下這個問題:shiro是如何保存當前會話認證狀態的,是上述中的某種實現方式,仍是shiro有另外的實現方式
每次請求都會生成新的subject,若是咱們把認證狀態只放到subject中,那麼每次請求都須要進行認證,這顯然是不合理的,咱們須要將認證狀態保存到會話(session)中,那麼整個會話期間只須要認證一次便可。那麼shiro是怎麼實現的了,咱們來看看源碼,從咱們Controller的doLogin方法開始
若是咱們繼續跟進s.setAttribute(attributeKey, value),會發現認證狀態最終存放到了SimpleSession的attributes屬性中,
private transient Map<Object, Object> attributes;
也就是說認證狀態會存在session的attributes中,正是咱們上面說的方式之一,shiro沒有用它特有的方式,最終在session中的存在形式以下圖
看過上篇博客的朋友應該會有印象:FormAuthenticationFilter的isAccessAllowed方法(從AuthenticatingFilter繼承)中第一個認證的是subject的authenticated
爲何獲取subject的authenticated,而不是直接獲取session的認證狀態,我還沒弄清楚爲何,難道是爲了組件的分工明確? 既然shiro這麼作了確定有它的道理,咱們先不糾結這個(知道的朋友能夠評論區提示下)。咱們知道登陸成功後,subject的authenticated會被賦值成true,可是登陸成功後的其餘請求,好比:http://localhost:8080/own/index,subject的authenticated是何時在哪被賦值成true的呢?咱們已經知道session中有認證狀態,那麼確定是某個時候在某個地方將session中的認證狀態賦值給了subject,具體是怎麼樣咱們來跟一下源碼,還記得shiro的入口filter:SpringShiroFilter,咱們從SpringShiroFilter的doFilterInternal(從AbstractShiroFilter繼承)方法開始
能夠看到,在建立subject的時候,會將session中的認證狀態賦值給subject的authenticated。
小結下:登陸時,登陸成功會將認證狀態(成功)存儲到session的attributes中,以後的每一次請求,在建立subject的時候,都會將session中的認證狀態賦值給subject的authenticated,那麼FormAuthenticationFilter在認證的時候會直接返回true,繼續走servlet filter鏈,最終來到咱們的Controller。
至此,該問題就明瞭了,會話認證狀態仍是保存在session中,只是中間處理的時候會將session中的認證狀態賦值給subject,由subject傳遞給FormAuthenticationFilter認證狀態。
若是咱們明白了上個問題,那麼這個問題就很好理解了。若是session過時,那麼經過sessionDAO獲取的session爲null,subject的authenticated就會被賦值成false,那麼在FormAuthenticationFilter中認證不經過,則會重定向到/login,讓用戶從新進行登陸認證。事實是這樣嗎,咱們來跟下源碼(假設此時請求是:http://localhost:8080/own/index)
能夠看到,請求進過SpringShiroFilter時,subject中authenticated被設置成false,而後生成ProxiedFilterChain
請求會來到FormAuthenticationFilter,認證不經過,重定向到/login,並返回false,表示filter鏈不用繼續往下走了(具體可查看上篇博文)。
強調下:不少對session的操做,都會同步到緩存(或持久層),包括session刷新(touch())、設置session屬性(setAttribute()等等,具體能夠看AbstractNativeSessionManager,不少session操做中都會調用onChange方法
protected void onChange(Session session) { sessionDAO.update(session); }
SessionManager負責session的操做,包括建立、維護、刪除、失效、驗證等;
AbstractNativeSessionManager的start是建立session的入口;
SimpleSession是shiro完徹底全的本身實現,是shiro對session的一種拓展。但SimpleSession不對外暴露,咱們通常操做的是SimpleSession的代理:DelegatingSession,或者是DelegatingSession的代理:StoppingAwareProxiedSession;對session的操做,會經過一層層代理,來到DelegatingSession,DelegatingSession將session的操做轉交給sessionMananger,sessionManager經過一些校驗後,最後轉交給SimpleSession處理。
通常操做的session是session的代理,代理將session操做委託給sessionManager,sesionManager校驗以後再轉交給SimpleSession;
session過時定時任務默認60分鐘執行一次,所session已過時或不合法,則拋出對應的異常,上層經過捕獲異常從sessionDAO中刪除session;
不僅定時任務作session的校驗,session的基本操做都在sessionManager中有作session的校驗,例如touch、setAttribute等,具體能夠查看AbstractNativeSessionManager,對session的操做都是經過AbstractNativeSessionManager處理後轉交給SimpleSession。
session共享實現的原理其實都是同樣的,都是filter + HttpServletRequestWrapper,只是實現細節會有所區別;
shiro的session共享實際上是比較簡單的,重寫CacheManager,將其操做指向咱們的redis,而後定製CachingSessionDAO實現session緩存操做和session持久化;
若是session須要持久化,推薦自定義sessionDAO繼承EnterpriseCacheSessionDAO,若是隻是緩存,則推薦自定義sessionDAO繼承CachingSessionDAO。
SpringShiroFilter註冊到spring容器,會被包裝成FilterRegistrationBean,經過FilterRegistrationBean註冊到servlet容器;SpringShiroFilter至關因而整個shiro的入口;
SpringShiroFilter會建立ProxiedFilterChain,代理servlet FilterChain,讓請求先走shiro的filter鏈,讓後再走servlet FilterChain。
認證經過Filter實現,anon表示匿名訪問,不須要認證,通常就是針對遊客能夠訪問的資源,而authc則表示須要登陸認證;
咱們全部的請求通常由shiro中3個Filter:LogoutFilter、AnonymousFilter、FormAuthenticationFilter分攤了,LogoutFilter負責/logout,AnonymousFilter負責/login和靜態資源,FormAuthenticationFilter則負責剩下的(/**);
認證由FormAuthenticationFilter實現,未登陸的請求會由它重定向到/login;認證過程是將界面輸入的信息(UsernamePasswordToken)與緩存(或數據庫)中的authenticationInfo進行匹對驗證;認證信息不建議緩存;
受權由註解方式,配合aop實現目標方法前的加強織入;認證過程是將緩存(或數據庫)中的authorizationInfo與@RequiresPermissions("xxx")中的xxx進行匹配校驗;認證信息建議緩存起來。
《跟我學shiro》