需求
公司打算重構權限系統,主要因爲現有的系統存在這些問題:
查詢用戶步驟繁瑣:選系統(主要是這一點)——用戶名——查詢按鈕;
簡化權限只分配到角色:現在權限全部彙總到一起,不知道權限從何而來;
角色承擔職責過多,雜而亂;
登錄名不唯一,無賬號找回功能
…
總之就是一個很亂的權限系統,考慮到security的強大功能,靈活易拓展(各種自定義),耦合性低,所以打算用security去是實現。
其實,我是不建議大家直接去了解security,大家可以先考慮一下沒引入security之前,登錄認證授權流程是怎樣的,因爲畢竟security也是按照這些去封裝代碼的。
如果直接去了解security的話,多少會有點迷茫和含糊,因爲它比較複雜(功能特別強大,拓展性極好),在知道本質之後去了解,一切都會顯得很清晰~~~
爲了方便入門,下面我主要講解的是沒有被security封裝之前的登錄授權流程代碼~~
如果這些您已瞭解,那麼請直接看下篇的登錄流程源碼分析。
前提
在瞭解security之前,我們必須知道這幾個東西:
-
什麼是會話?
-
授權的數據模型?
-
RBAC方式
什麼是會話?
用戶認證通過後,爲了避免用戶的每次操作都進行認證可將用戶的信息保證在會話中。會話就是系統爲了保持當前 用戶的登錄狀態所提供的機制。
常見的方式有兩種:
- 基於session方式;
- 基於token方式;
session方式,我們可能很熟悉了,它的交互流程就是,用戶認證成功後,在服務端生成用戶相關的數據保存在session(當前會話)中,發給客戶端的 sesssion_id 存放到 cookie 中,這樣用戶客戶端請求時帶上 session_id 就可以驗證服務器端是否存在 session 數 據,以此完成用戶的合法校驗,當用戶退出系統或session過期銷燬時,客戶端的session_id也就無效了。如圖:
基於token方式如下圖:
它的交互流程是,用戶認證成功後,服務端生成一個token發給客戶端,客戶端可以放到 cookie 或 localStorage 等存儲中,每次請求時帶上 token,服務端收到token通過驗證後即可確認用戶身份
基於session的認證方式由Servlet規範定製,服務端要存儲session信息需要佔用內存資源,客戶端需要支持 cookie;基於token的方式則一般不需要服務端存儲token,並且不限制客戶端的存儲方式。如今移動互聯網時代 更多類型的客戶端需要接入系統,系統多是採用前後端分離的架構進行實現,所以基於token的方式更適合。
授權的數據模型
即表結構的對應關係
主體(用戶id、賬號、密碼、…)
角色(角色id、角色名稱、…)
主體(用戶)和角色關係(用戶id、角色id、…)
權限(權限id、權限標識、權限名稱、資源名稱、資源訪問地址、…)
角色與權限的關係(角色id,權限id…)
RBAC方式
如何實現授權,可以通過RBAC方式實現授權,RBAC有兩種方式:
- 基於角色的訪問控制(存在硬編碼問題)
- 基於資源的訪問控制(推薦)
具體如下:
基於角色的訪問控制
用戶必須 具有查詢工資權限纔可以查詢員工工資信息等,訪問控制流程如下:
根據上圖中的判斷邏輯,授權代碼可表示如下:
if(主體.hasRole("總經理角色id")){
查詢工資
}
如果上圖中查詢工資所需要的角色變化爲總經理和部門經理,此時就需要修改判斷邏輯爲“判斷用戶的角色是否是 總經理或部門經理”,修改代碼如下:
if(主體.hasRole("總經理角色id") || 主體.hasRole("部門經理角色id")){
查詢工資
}
由此,我們可以看出,以上存在硬編碼問題!
基於資源的訪問控制
根據上圖中的判斷,授權代碼可以表示爲:
if(主體.hasPermission("查詢工資權限標識")){
查詢工資
}
優點:系統設計時定義好查詢工資的權限標識,即使查詢工資所需要的角色變化爲總經理和部門經理也不需要修改 授權代碼,系統可擴展性強
入門
在瞭解security之前,我們先來了解一下沒有security的時候,登錄流程是怎麼走的。
爲了防止迷路,我先說一下步驟:
-
搭建項目環境(applicationcontext.xml,springmvc.xml,登錄頁面,初始化spring容器等);
-
配置所需要的bean以及業務;
-
認證邏輯;
-
會話管理
- 基於session,通過HttpSession操作,主要用到的方法有setAttribute(),getAttribute(),invalidate();
- 在認證成功的時候調用setAttribute()方法儲存,在訪問資源的時候調用getAttribute(),在登出的時候調用invalidate();
-
授權功能
其實授權的本質就是一個攔截器,通過頁面當前訪問的資源與後臺查到的用戶權限做匹配,如果一致,則放行。
以上部分我們重點講解3,5其餘步驟略。
認證邏輯
認證的流程爲:
- 首先,當用戶請求過來的時候,我們是不是需要判斷一下它傳過來的值是否爲空,即用戶名或者密碼爲空;
- 其次我們需要拿着用戶名字去我們數據庫查詢是否有這個用戶,沒有的話,直接拋異常“沒有該用戶”;
- 如果通過用戶名查到用戶的話,那我們是不是需要用它來跟用戶輸入的值去做對比,如果一致則放行,不一致則拋異常(“賬號或密碼錯誤”);
以上三點用代碼來表示的話,如下:
public User authentication(AuthenticationRequest authenticationRequest){
if(authenticationRequest == null || StringUtils.isEmpty(authenticationRequest.getUsername()) || StringUtils.isEmpty(authenticationRequest.getPassword())){
throw new RuntimeException("用戶名或密碼爲空");
}
User user = userDao.getUserByUsername(authenticationRequest.getUsername());
if(Objects.isNull(user)){
throw new RuntimeException("該用戶不存在!");
}
if(!authenticationRequest.getPassword().equals(a.getPassword())){
throw new RuntimeException("用戶名或密碼錯誤!");
}
return user;
}
其實這段代碼被security封裝到了一個叫userDetailService的類中,但是其最本質的功能跟這個是一樣的;
授權功能
流程如下:
- 爲了模仿授權功能的實現,我們增加一個r1的資源(訪問此路徑的前提是在用戶已認證的情況下),想要訪問此資源必須要有p1的權限;
- 現在一名叫‘王五’的用戶想訪問它,所以我們就需要判斷一下王五是否含有p1權限;
- 如果含有p1權限,則放行;
代碼如下:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//用戶身份信息
Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if(object == null){
//沒有認證
writeContent(response,"請登錄");
}
UserDto userDto = (UserDto) object;
//請求的url
String requestURI = request.getRequestURI();
if( userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")){
return true;
}
writeContent(response,"沒有權限,拒絕訪問");
return false;
}
其實這塊代碼也被security封裝到了userDetailService中,但是security可能跟這個有點出入,security它引入了一個AccessDecisionVoter用於投票選舉機制,又採用3種模式判定,這個我們在後面詳細解釋…
總結
至此,我們security種的認證授權最本質的代碼就說到這了,當然security的功能要比這強大太多,下一結我們去看看security的底層源碼,看看它就是是怎麼進行登錄授權流程的。
下一篇鏈接:SpringBoot集成SpringSecurity(二) 登錄認證流程解析
參考文獻
推薦視頻:https://www.bilibili.com/video/av73730658?from=search&seid=10438598876647660573
因爲我就是用這個視頻去入手的,特別適合入門,講的很透徹,大家感興趣的可以看看
另:如有總結不對的地方,麻煩大家指出,希望我們共同給進步_