SpringBoot集成Shiro(一)驗證用戶登錄驗證
SpringBoot集成Shiro(三)驗證用戶權限
SpringBoot集成Shiro(四)驗證用戶角色升級版
Shiro 這個安全認證框架已經幫我們做了很多事情,在一般情況下,我們完全可以將它當做一個黑盒來使用。
在上一篇文章中,我們通過 Shiro 完成了用戶登錄驗證功能,這篇文章將在它的基礎上增加角色的控制。角色控制也很簡單,大致分爲以下幾步:
- 增加想要角色控制的資源
- Shiro配置
- 角色設置
增加需要角色控制的資源
我們首先在 UserController 類下增加一個方法,它的訪問URL就是 /user/getAllUser,我們將對這個方法進行角色控制:
@RequestMapping("getAllUser")
public String getAllUserInfo(){
return "This is all user information.";
}
Shiro 配置
那麼哪些角色可以訪問到這個方法呢?需要我們在 ShiroConfig 配置類中 shiroFilter 方法裏面增加配置(完整配置在下方):
// 1.創建攔截器 Map,按照順序攔截,所以用 LinkedHashMap
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 2.配置不會被攔截的 URL,按照順序判斷,anon:所有url都都可以匿名訪問
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/register", "anon");
// 3.配置需要角色驗證的 URL
filterChainDefinitionMap.put("/user/getAllUser", "roles[admin,developer]");
filterChainDefinitionMap.put("/**", "anon");
// 將攔截器綁定到 shiroFilterFactoryBean上
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
我們在配置攔截器的時候,會使用一個 Map,而且要按照順序攔截,所以用 LinkedHashMap。
我們通過 Map 來指定這一個或者這一類 URL 使用哪個或哪些攔截器來進行攔截。Map 的 key 代表的是 要訪問 URL,URL支持通配符!所以可以指定一類URL。
Map 的 value 代表的是使用到的攔截器。這個 value 不是隨便寫的哦。它是我們攔截器的簡寫名稱,下面我列舉出這些攔截器的簡稱和具體類的對應關係:
Filter Name | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
這裏我們將登陸註冊接口和靜態資源放行,這些資源不用認證就能訪問。然後對要控制的資源 /user/getAllUser 使用 roles 過濾器,它對應的類就是 org.apache.shiro.web.filter.authz.RolesAuthorizationFilter。中括號裏面兩個字符串代表:這個資源需要有 admin 角色和 developer 角色才能進行訪問。
注意:攔截器過濾鏈定義是從上向下順序執行,一般將 “/**” 放在最爲下邊,這是一個坑呢,一不小心代碼就不好使了。
提醒:如果要對一個URL進行多個攔截器的攔截,那麼在 Map 的 value 裏面使用逗號分隔。例如:
filterChainDefinitionMap.put("/user/getAllUser", “authc,roles[admin,developer]”);
接下來配置當我們未進行認證時去訪問需要認證後才能訪問的資源和角色授權失敗後,Shiro做的處理:
// 跳轉登錄界面,如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/myLogin");
//未授權界面,在授權失敗後會跳轉到指定界面
shiroFilterFactoryBean.setUnauthorizedUrl("/Unauthorized");
也就是說,如果我們沒有登陸就直接訪問 /user/getAllUser 這個資源的話,Shiro 會幫我們自動攔截並跳轉到登陸界面。如果角色授權失敗了,就會跳轉到我們上面配置的 Unauthorized 界面去。我們待會兒會進行驗證看看結果就明白了。
至於 Shiro 是怎麼知道我們有沒有進行認證的呢?其實是 Shiro 就是通過 Session 進行會話管理的,當用戶登錄之後會生成一個 sessionId 返回給前端,前端保存在 cookie 裏面,下次請求時攜帶上這個 sessionId,最後Shiro 在驗證這個SessionId合不合法將就行了。
shiroFilter完整配置如下:
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
// 創建 ShiroFilterFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 將SecurityManager實例 並綁定給 ShiroFilterFactoryBean
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 創建攔截器 Map,按照順序攔截,所以用 LinkedHashMap
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不會被攔截的鏈接 按照順序判斷
filterChainDefinitionMap.put("/static/**", "anon");
// 過濾鏈定義,從上向下順序執行,一般將 "/**" 放在最爲下邊:這是一個坑呢,一不小心代碼就不好使了;
// anon:所有url都都可以匿名訪問
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/register", "anon");
filterChainDefinitionMap.put("/user/getAllUser", "roles[admin,developer]");
filterChainDefinitionMap.put("/**", "anon");
// 登錄界面,如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/myLogin");
//未授權界面,在授權失敗後會跳轉到指定界面
shiroFilterFactoryBean.setUnauthorizedUrl("/Unauthorized");
// 將攔截器綁定到 shiroFilterFactoryBean上
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
好了,現在我們已經明確的知道了哪些資源需要哪些角色才能訪問,那麼問題來了,我們如何把用戶擁有的角色和Shiro配置裏面的東西進行對應起來呢?
角色授權
接下來我們就來完成這塊的邏輯,在此之前,先來回憶一下,在上一篇文章中,我們的 UserRealm 類裏有一個授權方法沒有實現,而現在,我們就要來實現它。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 可以通過下面這個方法拿到前端傳過來的用戶名,然後根據這個用戶名去數據庫查詢用戶信息
// String username = (String) principalCollection.getPrimaryPrincipal();
// 創建授權器
SimpleAuthorizationInfo sao = new SimpleAuthorizationInfo();
/* 模擬數據庫查詢出來的角色信息 */
Set<String> set = new HashSet<>();
set.add("admin");
set.add("developer");
// 將角色信息設置進授權器。
sao.setRoles(set);
return sao;
}
首先將當前用戶擁有的角色信息查詢出來,然後再將角色數據設置進授權器即可,這樣 Shiro 就能自動幫我們進行角色認證了。
測試
不登錄直接訪問受限資源
接下來我們就進入測試,我們啓動服務後,先不登錄,而直接訪問一下 /user/getAllUser 這個資源,看看有什麼效果:
可以看出沒有經過登錄認證的話,直接訪問受限資源會被 Shiro 攔截,出現 404 ,爲什麼會出現 404 呢?因爲我們在上面配置裏面配置了,如果認證失敗就會跳轉到我們指定的 “myLogin” 登錄界面。可是我們沒有這個界面,所以就出現了404.
在此我也建議:我們一般不會讓後端去控制頁面的跳轉,我們會返回特定的狀態碼和消息給前端,前端根據不同的情況去跳轉頁面。這樣處理比較好。
先登錄再訪問受限資源
現在我們測試認證過後訪問資源的效果,先進行登錄:
登錄成功,下面訪問資源:
可以看到訪問成功了,成功的拿到了返回值。
權限不夠訪問受限資源
因爲我們使用的是 Shiro 自帶的角色驗證規則,它的規則是——hasAllRoles,也就是說當前進行角色授權的用戶,他必須擁有配置中的所有角色,可以多不可以少。舉個例子:
配置文件中要求的角色是 admin 和 developer。如果用戶只擁有兩者其一的話,是會授權失敗的。用戶必須同時擁有 admin 和 developer 纔可以。如果用戶擁有 admin 和 developer 和 manager 三種角色也是可以的。
我們驗證一下,我們將代碼中用戶的角色註釋一個,然後重啓服務,再登錄,最後來訪問看看:
/* 模擬數據庫查詢出來的角色信息 */
Set<String> set = new HashSet<>();
set.add("admin");
//set.add("developer");
Shiro 自帶的角色驗證規則難免有點要求過嚴了,那麼有沒有辦法做到,用戶只擁有兩者之一就可以呢?答案是當然可以,我們在後續的角色授權升級版中會講到,如何自定義角色驗證規則,和上面提到的角色認證失敗時不進行跳轉,而只返回指定消息給前端。
完整代碼可到此處下載:springboot-shiro-role-demo
技 術 無 他, 唯 有 熟 爾。
知 其 然, 也 知 其 所 以 然。
踏 實 一 些, 不 要 着 急, 你 想 要 的 歲 月 都 會 給 你。