apache shiro 是一個功能強大且靈活的開放源代碼安全框架,可以細粒度地處理認證 (authentication),授權 (authorization),會話 (session) 管理和加密 (cryptography) 等企業級應用中常見的安全控制流程。 apache shiro 的首要目標是易于使用和理解。 有時候安全性的流程控制會非常復雜,對開發人員來說是件很頭疼的事情,但并不一定如此。 框架就應該盡可能地掩蓋復雜性,并公開一個簡潔而直觀的 api,從而簡化開發人員的工作,確保其應用程序安全性。這次我們聊一聊如何在 spring web 應用中使用 shiro 實現權限控制。
功能
apache shiro 是一個具有許多功能的綜合型應用程序安全框架。 下圖為 shiro 中的最主要的幾個功能:

shiro 的主要目標是“應用安全的四大基石” - 認證,授權,會話管理和加密:
- 身份驗證:也就是通常所說的 “登錄”,為了證明用戶的行為所有者。
- 授權:訪問控制的過程,即確定什么用戶可以訪問哪些內容。
- 會話管理:即使在非 web 應用程序中,也可以管理用戶特定的會話,這也是 shiro 的一大亮點。
- 加密技術:使用加密算法保證數據的安全,非常易于使用。
架構
從整體概念上理解,shiro 的體系架構有三個主要的概念:subject (主體,也就是用戶),security manager (安全管理器)和 realms (領域)。 下圖描述了這些組件之間的關系:

這幾大組件可以這樣理解:
- subject (主體):主體是當前正在操作的用戶的特定數據集合。主體可以是一個人,也可以代表第三方服務,守護進程,定時任務或類似的東西,也就是幾乎所有與該應用進行交互的事物。
- security manager (安全管理器):它是 shiro 的體系結構的核心,扮演了類似于一把 “傘” 的角色,它主要負責協調內部的各個組件,形成一張安全網。
- realms (領域):shiro 與應用程序安全數據之間的 “橋梁”。當需要實際與用戶帳戶等安全相關數據進行交互以執行認證和授權時,shiro 將從 realms 中獲取這些數據。
數據準備
在 web 應用中,對安全的控制主要有角色、資源、權限(什么角色能訪問什么資源)幾個概念,一個用戶可以有多個角色,一個角色也可以訪問多個資源,也就是角色可以對應多個權限。落實到數據庫設計上,我們至少需要建 5 張表:用戶表、角色表、資源表、角色-資源表、用戶-角色表,這 5 張表的結構如下:
用戶表:
id | username | password |
---|---|---|
1 | 張三 | 123456 |
2 | 李四 | 666666 |
3 | 王五 | 000000 |
角色表:
id | rolename |
---|---|
1 | 管理員 |
2 | 經理 |
3 | 員工 |
資源表:
id | resname |
---|---|
1 | /user/add |
2 | /user/delete |
3 | /compony/info |
角色-資源表:
id | roleid | resid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
用戶-角色表:
id | userid | roleid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
對應的 pojo 類如下:
1
2
3
4
5
6
7
8
9
|
/** * 用戶 */ public class user { private integer id; private string username; private string password; //getter & setter... } |
1
2
3
4
5
6
7
|
/** * 角色 */ public class role { private string id; private string rolename; } |
1
2
3
4
5
6
7
|
/** * 資源 */ public class resource { private string id; private string resname; } |
1
2
3
4
5
6
7
8
|
/** * 角色-資源 */ public class roleres { private string id; private string roleid; private string resid; } |
1
2
3
4
5
6
7
8
|
/** * 用戶-角色 */ public class userrole { private string id; private string userid; private string roleid; } |
spring 與 shiro 整合的詳細步驟,請參閱我的博客 《 spring 應用中整合 apache shiro 》 。 這里補充一下:需要提前引入 shiro 的依賴,打開mvnrepository.com,搜索 shiro,我們需要前三個依賴,也就是 shiro-core、shiro-web 以及 shiro-spring,以 maven 項目為例,在 pom.xml
中的 <dependencies>
節點下添加如下依賴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-core</artifactid> <version> 1.4 . 0 </version> </dependency> <dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-web</artifactid> <version> 1.4 . 0 </version> </dependency> <dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-spring</artifactid> <version> 1.4 . 0 </version> </dependency> |
在 application-context.xml
中需要這樣配置 shirofilter
bean:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!-- 配置shiro的過濾器工廠類,id- shirofilter要和我們在web.xml中配置的過濾器一致 --> <bean id= "shirofilter" class = "org.apache.shiro.spring.web.shirofilterfactorybean" > <property name= "securitymanager" ref= "securitymanager" /> <!-- 登錄頁面 --> <property name= "loginurl" value= "/login" /> <!-- 登錄成功后的頁面 --> <property name= "successurl" value= "/index" /> <!-- 非法訪問跳轉的頁面 --> <property name= "unauthorizedurl" value= "/403" /> <!-- 權限配置 --> <property name= "filterchaindefinitions" > <value> <!-- 無需認證即可訪問的靜態資源,還可以添加其他 url --> / static /** = anon <!-- 除了上述忽略的資源,其他所有資源都需要認證后才能訪問 --> /** = authc </value> </property> </bean> |
接下來就需要定義 realm 了,自定義的 realm 集成自 authorizingrealm
類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
public class myrealm extends authorizingrealm { @autowired private userservice userservice; /** * 驗證權限 */ @override protected authorizationinfo dogetauthorizationinfo(principalcollection principalcollection) { string loginname = securityutils.getsubject().getprincipal().tostring(); if (loginname != null ) { string userid = securityutils.getsubject().getsession().getattribute( "usersessionid" ).tostring(); // 權限信息對象,用來存放查出的用戶的所有的角色及權限 simpleauthorizationinfo info = new simpleauthorizationinfo(); // 用戶的角色集合 shirouser shirouser = (shirouser) principalcollection.getprimaryprincipal(); info.setroles(shirouser.getroles()); info.addstringpermissions(shirouser.geturlset()); return info; } return null ; } /** * 認證回調函數,登錄時調用 */ protected authenticationinfo dogetauthenticationinfo(authenticationtoken token) { string username = (string) token.getprincipal(); user user = new user(); sysuser.setusername(username); try { list<sysuser> users = userservice.findbynames(user); list<string> rolelist= userservice.selectrolenamelistbyuserid(users.get( 0 ).getid()); if (users.size() != 0 ) { string pwd = users.get( 0 ).getpassword(); // 當驗證都通過后,把用戶信息放在 session 里 session session = securityutils.getsubject().getsession(); session.setattribute( "usersession" , users.get( 0 )); session.setattribute( "usersessionid" , users.get( 0 ).getid()); session.setattribute( "userroles" , org.apache.commons.lang.stringutils.join(rolelist, "," )); return new simpleauthenticationinfo(username,users.get( 0 ).getpassword()); } else { // 沒找到該用戶 throw new unknownaccountexception(); } } catch (exception e) { system.out.println(e.getmessage()); } return null ; } /** * 更新用戶授權信息緩存. */ public void clearcachedauthorizationinfo(principalcollection principals) { super .clearcachedauthorizationinfo(principals); } /** * 更新用戶信息緩存. */ public void clearcachedauthenticationinfo(principalcollection principals) { super .clearcachedauthenticationinfo(principals); } /** * 清除用戶授權信息緩存. */ public void clearallcachedauthorizationinfo() { getauthorizationcache().clear(); } /** * 清除用戶信息緩存. */ public void clearallcachedauthenticationinfo() { getauthenticationcache().clear(); } /** * 清空所有緩存 */ public void clearcache(principalcollection principals) { super .clearcache(principals); } /** * 清空所有認證緩存 */ public void clearallcache() { clearallcachedauthenticationinfo(); clearallcachedauthorizationinfo(); } } |
最后定義一個用戶登錄的控制器,接受用戶的登錄請求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@controller public class usercontroller { /** * 用戶登錄 */ @postmapping ( "/login" ) public string login( @valid user user,bindingresult bindingresult,redirectattributes redirectattributes){ try { if (bindingresult.haserrors()){ return "login" ; } //使用權限工具進行認證,登錄成功后跳到 shirofilter bean 中定義的 successurl securityutils.getsubject().login( new usernamepasswordtoken(user.getusername(), user.getpassword())); return "redirect:index" ; } catch (authenticationexception e) { redirectattributes.addflashattribute( "message" , "用戶名或密碼錯誤" ); return "redirect:login" ; } } /** * 注銷登錄 */ @getmapping ( "/logout" ) public string logout(redirectattributes redirectattributes ){ securityutils.getsubject().logout(); return "redirect:login" ; } } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://juejin.im/post/5abf92b96fb9a028c368ea50