shiro源碼篇 - 疑問解答與系列總結,你值得擁有


前言

  開心一刻

    小明的朋友骨折了,小明去他家里看他。他老婆很細心的為他換葯,敷葯,然后出去買菜。小明滿臉羡慕地說:你特么真幸福啊,你老婆對你那么好!朋友哭得稀里嘩啦的說:兄弟你別說了,我幸福個錘子,就是她把我打骨折的。

揣摩下此刻男人的內心

  路漫漫其修遠兮,吾將上下而求索!

  github:https://github.com/youzhibing

  碼雲(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,另外認證頻率本來就不高,緩存的意義不大。

  一般情況下授權是通過注解方式實現的,注解配合aop會在我們的業務方法前織入前置權限檢查處理,檢查過程與認證過程類似:從緩存中獲取authorizationInfo,沒有則通過realm從數據庫獲取,然后放入緩存,然后將authorizationInfo與@RequiresPermissions("xxx")中的xxx來進行匹配,完成權限檢查,檢查通過則進入我們的目標方法,不通過則拋出異常。關於權限緩存,個人建議開啟,因為權限的驗證還是挺頻繁的,如果不開啟緩存,那么會給數據庫造成一定的壓力。

遺留問題解答

  上篇遺留問題:session過期后,我們再請求,shiro是如何處理並跳轉到登錄頁的?回答這個問題之前,我們先看看另外一個問題:

上篇博文中講到了登錄認證成功后會將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有另外的實現方式

  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過期后,我們再請求,shiro是如何處理並跳轉到登錄頁的

    如果我們明白了上個問題,那么這個問題就很好理解了。如果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);
}

shiro源碼系列

  系列地址

  shiro源碼篇 - shiro的session創建,你值得擁有

    SessionManager負責session的操作,包括創建、維護、刪除、失效、驗證等;

    AbstractNativeSessionManager的start是創建session的入口;

    SimpleSession是shiro完完全全的自己實現,是shiro對session的一種拓展。但SimpleSession不對外暴露,我們一般操作的是SimpleSession的代理:DelegatingSession,或者是DelegatingSession的代理:StoppingAwareProxiedSession;對session的操作,會通過一層層代理,來到DelegatingSession,DelegatingSession將session的操作轉交給sessionMananger,sessionManager通過一些校驗后,最后轉交給SimpleSession處理。

  shiro源碼篇 - shiro的session的查詢、刷新、過期與刪除,你值得擁有

    一般操作的session是session的代理,代理將session操作委托給sessionManager,sesionManager校驗之后再轉交給SimpleSession;

    session過期定時任務默認60分鍾執行一次,所session已過期或不合法,則拋出對應的異常,上層通過捕獲異常從sessionDAO中刪除session;

    不只定時任務做session的校驗,session的基本操作都在sessionManager中有做session的校驗,例如touch、setAttribute等,具體可以查看AbstractNativeSessionManager,對session的操作都是通過AbstractNativeSessionManager處理后轉交給SimpleSession。

  shiro源碼篇 - shiro的session共享,你值得擁有

    session共享實現的原理其實都是一樣的,都是filter + HttpServletRequestWrapper,只是實現細節會有所區別;

    shiro的session共享其實是比較簡單的,重寫CacheManager,將其操作指向我們的redis,然后定制CachingSessionDAO實現session緩存操作和session持久化;

    如果session需要持久化,推薦自定義sessionDAO繼承EnterpriseCacheSessionDAO,如果只是緩存,則推薦自定義sessionDAO繼承CachingSessionDAO。

  shiro源碼篇 - shiro的filter,你值得擁有

    SpringShiroFilter注冊到spring容器,會被包裝成FilterRegistrationBean,通過FilterRegistrationBean注冊到servlet容器;SpringShiroFilter相當於是整個shiro的入口;

    SpringShiroFilter會創建ProxiedFilterChain,代理servlet FilterChain,讓請求先走shiro的filter鏈,讓后再走servlet FilterChain。

  shiro源碼篇 - shiro認證與授權,你值得擁有

    認證通過Filter實現,anon表示匿名訪問,不需要認證,一般就是針對游客可以訪問的資源,而authc則表示需要登錄認證;

    我們所有的請求一般由shiro中3個Filter:LogoutFilter、AnonymousFilter、FormAuthenticationFilter分攤了,LogoutFilter負責/logout,AnonymousFilter負責/login和靜態資源,FormAuthenticationFilter則負責剩下的(/**);

    認證由FormAuthenticationFilter實現,未登錄的請求會由它重定向到/login;認證過程是將界面輸入的信息(UsernamePasswordToken)與緩存(或數據庫)中的authenticationInfo進行匹對驗證;認證信息不建議緩存;

    授權由注解方式,配合aop實現目標方法前的增強織入;認證過程是將緩存(或數據庫)中的authorizationInfo與@RequiresPermissions("xxx")中的xxx進行匹配校驗;認證信息建議緩存起來。

參考

  《跟我學shiro》


免責聲明!

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



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