說(shuō)明:本文很多觀點(diǎn)和內(nèi)容來(lái)自互聯(lián)網(wǎng)以及各種資料穗熬,如果侵犯了您的權(quán)益,請(qǐng)及時(shí)聯(lián)系我,我會(huì)刪除相關(guān)內(nèi)容臼勉。
權(quán)限管理
基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇餐弱,權(quán)限管理實(shí)現(xiàn)對(duì)用戶訪問(wèn)系統(tǒng)的控制宴霸,按照安全規(guī)則或者安全策略控制用戶可以訪問(wèn)而且只能訪問(wèn)自己被授權(quán)的資源。
權(quán)限管理包括用戶身份認(rèn)證和授權(quán)兩部分膏蚓,簡(jiǎn)稱認(rèn)證授權(quán)瓢谢。對(duì)于需要訪問(wèn)控制的資源用戶首先經(jīng)過(guò)身份認(rèn)證,認(rèn)證通過(guò)后用戶具有該資源的訪問(wèn)權(quán)限方可訪問(wèn)驮瞧。
- 用戶身份認(rèn)證
身份認(rèn)證氓扛,就是判斷一個(gè)用戶是否為合法用戶的處理過(guò)程。最常用的簡(jiǎn)單身份認(rèn)證方式是核對(duì)用戶輸入的用戶名和口令论笔,來(lái)判斷用戶身份是否正確采郎。對(duì)于采用指紋等系統(tǒng),則出示指紋狂魔;對(duì)于硬件Key等刷卡系統(tǒng)蒜埋,則需要刷卡。 -
用戶名密碼身份認(rèn)證流程
用戶名密碼身份認(rèn)證流程.png - 關(guān)鍵對(duì)象
上邊的流程圖中需要理解以下關(guān)鍵對(duì)象:
- Subject:主體
訪問(wèn)系統(tǒng)的用戶毅臊,主體可以是用戶理茎、程序等黑界,進(jìn)行認(rèn)證的都稱為主體; - Principal:身份信息
是主體(subject)進(jìn)行身份認(rèn)證的標(biāo)識(shí)皂林,標(biāo)識(shí)必須具有唯一性朗鸠,如用戶名、手機(jī)號(hào)础倍、郵箱地址等烛占,一個(gè)主體可以有多個(gè)身份,但是必須有一個(gè)主身份(Primary Principal)沟启。 - credential:憑證信息
只有主體自己知道的安全信息忆家,如密碼、證書(shū)等德迹。
-
授權(quán)
概念
授權(quán)芽卿,即訪問(wèn)控制,控制誰(shuí)能訪問(wèn)哪些資源胳搞。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問(wèn)系統(tǒng)的資源卸例,對(duì)于某些資源沒(méi)有權(quán)限是無(wú)法訪問(wèn)的。
授權(quán)流程:
下圖中橙色為授權(quán)流程
授權(quán)流程.png -
關(guān)鍵對(duì)象
授權(quán)可簡(jiǎn)單理解為who對(duì)what(which)進(jìn)行How操作:
-----Who肌毅,即主體(Subject)筷转,主體需要訪問(wèn)系統(tǒng)中的資源。
-----What悬而,即資源(Resource)呜舒,如系統(tǒng)菜單、頁(yè)面笨奠、按鈕袭蝗、類方法、系統(tǒng)商品信息等艰躺。資源包括資源類型和資源實(shí)例呻袭,比如商品信息為資源類型,類型為c01的商品為資源實(shí)例腺兴,編號(hào)為001的商品信息也屬于資源實(shí)例左电。
------How,權(quán)限/許可(Permission)页响,規(guī)定了主體對(duì)資源的操作許可篓足,權(quán)限離開(kāi)資源沒(méi)有意義,如用戶查詢權(quán)限闰蚕、用戶添加權(quán)限栈拖、某個(gè)類方法的調(diào)用權(quán)限、編號(hào)為001用戶的修改權(quán)限等没陡,通過(guò)權(quán)限可知主體對(duì)哪些資源都有哪些操作許可涩哟。
權(quán)限分為粗顆粒和細(xì)顆粒索赏,粗顆粒權(quán)限是指對(duì)資源類型的權(quán)限,細(xì)顆粒權(quán)限是對(duì)資源實(shí)例的權(quán)限贴彼。
主體潜腻、資源、權(quán)限關(guān)系如下圖:
主體器仗、資源融涣、權(quán)限關(guān)系圖.png
權(quán)限模型
對(duì)上節(jié)中的主體、資源精钮、權(quán)限通過(guò)數(shù)據(jù)模型表示威鹿。 - 主體(賬號(hào)、密碼)
- 資源(資源名稱轨香、訪問(wèn)地址)
- 權(quán)限(權(quán)限名稱忽你、資源id)
- 角色(角色名稱)
- 角色和權(quán)限關(guān)系(角色id、權(quán)限id)
- 主體和角色關(guān)系(主體id弹沽、角色id)
權(quán)限模型.png
通常企業(yè)開(kāi)發(fā)中將資源和權(quán)限表合并為一張權(quán)限表檀夹,如下:
資源(資源名稱、訪問(wèn)地址)
權(quán)限(權(quán)限名稱策橘、資源id)
合并為:
權(quán)限(權(quán)限名稱、資源名稱娜亿、資源訪問(wèn)地址) - 權(quán)限分配
對(duì)主體分配權(quán)限丽已,主體只允許在權(quán)限范圍內(nèi)對(duì)資源進(jìn)行操作,比如:對(duì)u01用戶分配商品修改權(quán)限买决,u01用戶只能對(duì)商品進(jìn)行修改沛婴。
權(quán)限分配的數(shù)據(jù)通常需要持久化,根據(jù)上邊的數(shù)據(jù)模型創(chuàng)建表并將用戶的權(quán)限信息存儲(chǔ)在數(shù)據(jù)庫(kù)中督赤。 - 權(quán)限控制
用戶擁有了權(quán)限即可操作權(quán)限范圍內(nèi)的資源嘁灯,系統(tǒng)不知道主體是否具有訪問(wèn)權(quán)限需要對(duì)用戶的訪問(wèn)進(jìn)行控制 -
基于角色的訪問(wèn)控制
RBAC基于角色的訪問(wèn)控制(Role-Based Access Control)是以角色為中心進(jìn)行訪問(wèn)控制,比如:主體的角色為總經(jīng)理可以查詢企業(yè)運(yùn)營(yíng)報(bào)表躲舌,查詢員工工資信息等丑婿,訪問(wèn)控制流程如下:
基于角色的訪問(wèn)控制.png
上圖中的判斷邏輯代碼可以理解為:
if(主體.hasRole("總經(jīng)理角色id")){
查詢工資
}
缺點(diǎn):以角色進(jìn)行訪問(wèn)控制粒度較粗,如果上圖中查詢工資所需要的角色變化為總經(jīng)理和部門(mén)經(jīng)理没卸,此時(shí)就需要修改判斷邏輯為“判斷主體的角色是否是總經(jīng)理或部門(mén)經(jīng)理”羹奉,系統(tǒng)可擴(kuò)展性差。
修改代碼如下:
if(主體.hasRole("總經(jīng)理角色id") || 主體.hasRole("部門(mén)經(jīng)理角色id")){
查詢工資
}
-
基于資源的訪問(wèn)控制
RBAC基于資源的訪問(wèn)控制(Resource-Based Access Control)是以資源為中心進(jìn)行訪問(wèn)控制约计,比如:主體必須具有查詢工資權(quán)限才可以查詢員工工資信息等诀拭,訪問(wèn)控制流程如下:
基于資源的訪問(wèn)控制.png
上圖中的判斷邏輯代碼可以理解為:
if(主體.hasPermission("wage:query")){
查詢工資
}
優(yōu)點(diǎn):系統(tǒng)設(shè)計(jì)時(shí)定義好查詢工資的權(quán)限標(biāo)識(shí),即使查詢工資所需要的角色變化為總經(jīng)理和部門(mén)經(jīng)理也只需要將“查詢工資信息權(quán)限”添加到“部門(mén)經(jīng)理角色”的權(quán)限列表中煤蚌,判斷邏輯不用修改耕挨,系統(tǒng)可擴(kuò)展性強(qiáng)细卧。
權(quán)限管理解決方案
- 什么是粗顆粒度和細(xì)顆粒度
對(duì)資源類型的管理稱為粗顆粒度權(quán)限管理,即只控制到菜單筒占、按鈕酒甸、方法,粗粒度的例子比如:用戶具有用戶管理的權(quán)限赋铝,具有導(dǎo)出訂單明細(xì)的權(quán)限插勤。對(duì)資源實(shí)例的控制稱為細(xì)顆粒度權(quán)限管理,即控制到數(shù)據(jù)級(jí)別的權(quán)限革骨,比如:用戶只允許修改本部門(mén)的員工信息农尖,用戶只允許導(dǎo)出自己創(chuàng)建的訂單明細(xì)。 - 如何實(shí)現(xiàn)粗顆粒度和細(xì)顆粒度
對(duì)于粗顆粒度的權(quán)限管理可以很容易做系統(tǒng)架構(gòu)級(jí)別的功能良哲,即系統(tǒng)功能操作使用統(tǒng)一的粗顆粒度的權(quán)限管理盛卡。
對(duì)于細(xì)顆粒度的權(quán)限管理不建議做成系統(tǒng)架構(gòu)級(jí)別的功能,因?yàn)閷?duì)數(shù)據(jù)級(jí)別的控制是系統(tǒng)的業(yè)務(wù)需求筑凫,隨著業(yè)務(wù)需求的變更業(yè)務(wù)功能變化的可能性很大滑沧,建議對(duì)數(shù)據(jù)級(jí)別的權(quán)限控制在業(yè)務(wù)層個(gè)性化開(kāi)發(fā),比如:用戶只允許修改自己創(chuàng)建的商品信息可以在service接口添加校驗(yàn)實(shí)現(xiàn)巍实,service接口需要傳入當(dāng)前操作人的標(biāo)識(shí)滓技,與商品信息創(chuàng)建人標(biāo)識(shí)對(duì)比,不一致則不允許修改商品信息棚潦。
-
基于url攔截
基于url攔截是企業(yè)中常用的權(quán)限管理方法令漂,實(shí)現(xiàn)思路是:將系統(tǒng)操作的每個(gè)url配置在權(quán)限表中,將權(quán)限對(duì)應(yīng)到角色丸边,將角色分配給用戶叠必,用戶訪問(wèn)系統(tǒng)功能通過(guò)Filter進(jìn)行過(guò)慮,過(guò)慮器獲取到用戶訪問(wèn)的url妹窖,只要訪問(wèn)的url是用戶分配角色中的url則放行繼續(xù)訪問(wèn)纬朝。
權(quán)限管理解決方案.png
shiro介紹
- 什么是shiro
Shiro是apache旗下一個(gè)開(kāi)源框架,它將軟件系統(tǒng)的安全認(rèn)證相關(guān)的功能抽取出來(lái)骄呼,實(shí)現(xiàn)用戶身份認(rèn)證共苛,權(quán)限授權(quán)、加密谒麦、會(huì)話管理等功能俄讹,組成了一個(gè)通用的安全認(rèn)證框架。 - 為什么要學(xué)shiro
使用shiro就可以非橙频拢快速的完成認(rèn)證患膛、授權(quán)等功能的開(kāi)發(fā),降低系統(tǒng)成本耻蛇。
shiro使用廣泛踪蹬,shiro可以運(yùn)行在web應(yīng)用胞此,非web應(yīng)用,集群分布式應(yīng)用中越來(lái)越多的用戶開(kāi)始使用shiro跃捣。
java領(lǐng)域中spring security(原名Acegi)也是一個(gè)開(kāi)源的權(quán)限管理框架漱牵,但是spring security依賴spring運(yùn)行,而shiro就相對(duì)獨(dú)立疚漆,最主要是因?yàn)閟hiro使用簡(jiǎn)單酣胀、靈活,所以現(xiàn)在越來(lái)越多的用戶選擇shiro娶聘。 -
Shiro架構(gòu)
shiro框架功能模塊.png
shiro架構(gòu).png
- Subject
Subject即主體闻镶,外部應(yīng)用與subject進(jìn)行交互,subject記錄了當(dāng)前操作用戶丸升,將用戶的概念理解為當(dāng)前操作的主體铆农,可能是一個(gè)通過(guò)瀏覽器請(qǐng)求的用戶,也可能是一個(gè)運(yùn)行的程序狡耻。 Subject在shiro中是一個(gè)接口墩剖,接口中定義了很多認(rèn)證授相關(guān)的方法,外部程序通過(guò)subject進(jìn)行認(rèn)證授夷狰,而subject是通過(guò)SecurityManager安全管理器進(jìn)行認(rèn)證授權(quán) - SecurityManager
SecurityManager即安全管理器岭皂,對(duì)全部的subject進(jìn)行安全管理,它是shiro的核心孵淘,負(fù)責(zé)對(duì)所有的subject進(jìn)行安全管理蒲障。通過(guò)SecurityManager可以完成subject的認(rèn)證、授權(quán)等瘫证,實(shí)質(zhì)上SecurityManager是通過(guò)Authenticator進(jìn)行認(rèn)證,通過(guò)Authorizer進(jìn)行授權(quán)庄撮,通過(guò)SessionManager進(jìn)行會(huì)話管理等背捌。
SecurityManager是一個(gè)接口,繼承了Authenticator, Authorizer, SessionManager這三個(gè)接口洞斯。 - Authenticator
Authenticator即認(rèn)證器毡庆,對(duì)用戶身份進(jìn)行認(rèn)證,Authenticator是一個(gè)接口烙如,shiro提供ModularRealmAuthenticator實(shí)現(xiàn)類么抗,通過(guò)ModularRealmAuthenticator基本上可以滿足大多數(shù)需求,也可以自定義認(rèn)證器亚铁。 - Authorizer
Authorizer即授權(quán)器蝇刀,用戶通過(guò)認(rèn)證器認(rèn)證通過(guò),在訪問(wèn)功能時(shí)需要通過(guò)授權(quán)器判斷用戶是否有此功能的操作權(quán)限徘溢。 - realm
Realm即領(lǐng)域吞琐,相當(dāng)于datasource數(shù)據(jù)源捆探,securityManager進(jìn)行安全認(rèn)證需要通過(guò)Realm獲取用戶權(quán)限數(shù)據(jù),比如:如果用戶身份數(shù)據(jù)在數(shù)據(jù)庫(kù)那么realm就需要從數(shù)據(jù)庫(kù)獲取用戶身份信息站粟。
注意:不要把realm理解成只是從數(shù)據(jù)源取數(shù)據(jù)黍图,在realm中還有認(rèn)證授權(quán)校驗(yàn)的相關(guān)的代碼。 - sessionManager
sessionManager即會(huì)話管理奴烙,shiro框架定義了一套會(huì)話管理助被,它不依賴web容器的session,所以shiro可以使用在非web應(yīng)用上切诀,也可以將分布式應(yīng)用的會(huì)話集中在一點(diǎn)管理检盼,此特性可使它實(shí)現(xiàn)單點(diǎn)登錄翘单。 - SessionDAO
SessionDAO即會(huì)話dao,是對(duì)session會(huì)話操作的一套接口书在,比如要將session存儲(chǔ)到數(shù)據(jù)庫(kù)竖般,可以通過(guò)jdbc將會(huì)話存儲(chǔ)到數(shù)據(jù)庫(kù)。 - CacheManager
CacheManager即緩存管理彩库,將用戶權(quán)限數(shù)據(jù)存儲(chǔ)在緩存鳞仙,這樣可以提高性能业稼。 - Cryptography
Cryptography即密碼管理,shiro提供了一套加密/解密的組件琴拧,方便開(kāi)發(fā)蚓胸。比如提供常用的散列、加/解密等功能。
-
shiro認(rèn)證
認(rèn)證流程.png
入門(mén)程序(用戶登陸和退出)
shiro.ini----->通過(guò)Shiro.ini配置文件初始化SecurityManager環(huán)境。
[users]
zhang=123
lisi=123
認(rèn)證代碼
// 用戶登陸唉工、用戶退出
@Test
public void testLoginLogout() {
// 構(gòu)建SecurityManager工廠雹熬,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環(huán)境
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro.ini");
// 通過(guò)工廠創(chuàng)建SecurityManager
SecurityManager securityManager = factory.getInstance();
// 將securityManager設(shè)置到運(yùn)行環(huán)境中
SecurityUtils.setSecurityManager(securityManager);
// 創(chuàng)建一個(gè)Subject實(shí)例,該實(shí)例認(rèn)證要使用上邊創(chuàng)建的securityManager進(jìn)行
Subject subject = SecurityUtils.getSubject();
// 創(chuàng)建token令牌谣膳,記錄用戶認(rèn)證的身份和憑證即賬號(hào)和密碼
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
// 用戶登陸
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 用戶認(rèn)證狀態(tài)
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);
// 用戶退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);
}
認(rèn)證執(zhí)行流程
1竿报、 創(chuàng)建token令牌,token中有用戶提交的認(rèn)證信息即賬號(hào)和密碼
2继谚、 執(zhí)行subject.login(token)烈菌,最終由securityManager通過(guò)Authenticator進(jìn)行認(rèn)證
3、 Authenticator的實(shí)現(xiàn)ModularRealmAuthenticator調(diào)用realm從ini配置文件取用戶真實(shí)的賬號(hào)和密碼,這里使用的是IniRealm(shiro自帶)
4芽世、 IniRealm先根據(jù)token中的賬號(hào)去ini中找該賬號(hào)挚赊,如果找不到則給ModularRealmAuthenticator返回null,如果找到則匹配密碼济瓢,匹配密碼成功則認(rèn)證通過(guò)荠割。
- 常見(jiàn)的異常
- UnknownAccountException
賬號(hào)不存在異常如下:
org.apache.shiro.authc.UnknownAccountException: No account found for user。葬荷。涨共。。 - IncorrectCredentialsException
當(dāng)輸入密碼錯(cuò)誤會(huì)拋此異常宠漩,如下:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials. - 更多如下:
DisabledAccountException(帳號(hào)被禁用)
LockedAccountException(帳號(hào)被鎖定)
ExcessiveAttemptsException(登錄失敗次數(shù)過(guò)多)
ExpiredCredentialsException(憑證過(guò)期)等
自定義Realm
上邊的程序使用的是Shiro自帶的IniRealm举反,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統(tǒng)的數(shù)據(jù)庫(kù)中讀取用戶信息扒吁,所以需要自定義realm火鼻。
shiro提供的realm
最基礎(chǔ)的是Realm接口,CachingRealm負(fù)責(zé)緩存處理雕崩,AuthenticationRealm負(fù)責(zé)認(rèn)證魁索,AuthorizingRealm負(fù)責(zé)授權(quán),通常自定義的realm繼承AuthorizingRealm盼铁。
- 自定義Realm
public class CustomRealm1 extends AuthorizingRealm {
@Override
public String getName() {
return "customRealm1";
}
//支持UsernamePasswordToken
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
//認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//從token中 獲取用戶身份信息
String username = (String) token.getPrincipal();
//拿username從數(shù)據(jù)庫(kù)中查詢
//....
//如果查詢不到則返回null
if(!username.equals("zhang")){//這里模擬查詢不到
return null;
}
//獲取從數(shù)據(jù)庫(kù)查詢出來(lái)的用戶密碼
String password = "123";//這里使用靜態(tài)數(shù)據(jù)模擬粗蔚。。
//返回認(rèn)證信息由父類AuthenticatingRealm進(jìn)行認(rèn)證
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, password, getName());
return simpleAuthenticationInfo;
}
//授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
}
shiro-realm.ini
[main]
#自定義 realm
customRealm=cn.itcast.shiro.authentication.realm.CustomRealm1
#將realm設(shè)置到securityManager
securityManager.realms=$customRealm
散列算法
散列算法一般用于生成一段文本的摘要信息饶火,散列算法不可逆鹏控,將內(nèi)容可以生成摘要,無(wú)法將摘要轉(zhuǎn)成原始內(nèi)容肤寝。散列算法常用于對(duì)密碼進(jìn)行散列当辐,常用的散列算法有MD5、SHA鲤看。
一般散列算法需要提供一個(gè)salt(鹽)與原始內(nèi)容生成摘要信息缘揪,這樣做的目的是為了安全性,比如:111111的md5值是:96e79218965eb72c92a549dd5a330112义桂,拿著“96e79218965eb72c92a549dd5a330112”去md5破解網(wǎng)站很容易進(jìn)行破解找筝,如果要是對(duì)111111和salt(鹽,一個(gè)隨機(jī)數(shù))進(jìn)行散列慷吊,這樣雖然密碼都是111111加不同的鹽會(huì)生成不同的散列值呻征。
例子
//md5加密,不加鹽
String password_md5 = new Md5Hash("111111").toString();
System.out.println("md5加密罢浇,不加鹽="+password_md5);
//md5加密,加鹽,一次散列
String password_md5_sale_1 = new Md5Hash("111111", "eteokues", 1).toString();
System.out.println("password_md5_sale_1="+password_md5_sale_1);
String password_md5_sale_2 = new Md5Hash("111111", "uiwueylm", 1).toString();
System.out.println("password_md5_sale_2="+password_md5_sale_2);
//兩次散列相當(dāng)于md5(md5())
//使用SimpleHash
String simpleHash = new SimpleHash("MD5", "111111", "eteokues",1).toString();
System.out.println(simpleHash);
在realm中使用
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//用戶賬號(hào)
String username = (String) token.getPrincipal();
//根據(jù)用戶賬號(hào)從數(shù)據(jù)庫(kù)取出鹽和加密后的值
//..這里使用靜態(tài)數(shù)據(jù)
//如果根據(jù)賬號(hào)沒(méi)有找到用戶信息則返回null嚷闭,shiro拋出異吃艿海“賬號(hào)不存在”
//按照固定規(guī)則加密碼結(jié)果 ,此密碼 要在數(shù)據(jù)庫(kù)存儲(chǔ)胞锰,原始密碼 是111111灾锯,鹽是eteokues
String password = "cb571f7bd7a6f73ab004a70322b963d5";
//鹽,隨機(jī)數(shù)嗅榕,此隨機(jī)數(shù)也在數(shù)據(jù)庫(kù)存儲(chǔ)
String salt = "eteokues";
//返回認(rèn)證信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, password, ByteSource.Util.bytes(salt),getName());
return simpleAuthenticationInfo;
}
shiro-cryptography.ini
[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數(shù)
credentialsMatcher.hashIterations=1
#將憑證匹配器設(shè)置到realm
customRealm=cn.itcast.shiro.authentication.realm.CustomRealm2
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
shiro授權(quán)
Shiro 支持三種方式的授權(quán):
- ν編程式:通過(guò)寫(xiě)if/else 授權(quán)代碼塊完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權(quán)限
} else {
//無(wú)權(quán)限
}
- 注解式:通過(guò)在執(zhí)行的Java方法上放置相應(yīng)的注解完成:
@RequiresRoles("admin")
public void hello() {
//有權(quán)限
}
- shiro 標(biāo)簽:在JSP/GSP 頁(yè)面通過(guò)相應(yīng)的標(biāo)簽完成:
<shiro:hasRole name="admin">
擁有admin角色才能看到我顺饮!
</shiro:hasRole>
授權(quán)測(cè)試------》創(chuàng)建存放權(quán)限的配置文件shiro-permission.ini,如下:
[users]
#用戶zhang的密碼是123凌那,此用戶具有role1和role2兩個(gè)角色
zhang=123,role1,role2
wang=123,role2
[roles]
#角色role1對(duì)資源user擁有create兼雄、update權(quán)限
role1=user:create,user:update
#角色role2對(duì)資源user擁有create、delete權(quán)限
role2=user:create,user:delete
#角色role3對(duì)資源user擁有create權(quán)限
role3=user:create
在ini文件中用戶帽蝶、角色赦肋、權(quán)限的配置規(guī)則是:“用戶名=密碼,角色1励稳,角色2…” “角色=權(quán)限1佃乘,權(quán)限2…”,首先根據(jù)用戶名找角色驹尼,再根據(jù)角色找權(quán)限趣避,角色是權(quán)限集合。
權(quán)限字符串規(guī)則
權(quán)限字符串的規(guī)則是:“資源標(biāo)識(shí)符:操作:資源實(shí)例標(biāo)識(shí)符”新翎,意思是對(duì)哪個(gè)資源的哪個(gè)實(shí)例具有什么操作程帕,“:”是資源/操作/實(shí)例的分割符,權(quán)限字符串也可以使用*通配符。
例子:
用戶創(chuàng)建權(quán)限:user:create吨述,或user:create:*
用戶修改實(shí)例001的權(quán)限:user:update:001
用戶實(shí)例001的所有權(quán)限:user:*:001
測(cè)試代碼
測(cè)試代碼同認(rèn)證代碼侍筛,注意ini地址改為shiro-permission.ini,主要學(xué)習(xí)下邊授權(quán)的方法敛苇,注意:在用戶認(rèn)證通過(guò)后執(zhí)行下邊的授權(quán)代碼。
@Test
public void testPermission() {
// 從ini文件中創(chuàng)建SecurityManager工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-permission.ini");
// 創(chuàng)建SecurityManager
SecurityManager securityManager = factory.getInstance();
// 將securityManager設(shè)置到運(yùn)行環(huán)境
SecurityUtils.setSecurityManager(securityManager);
// 創(chuàng)建主體對(duì)象
Subject subject = SecurityUtils.getSubject();
// 對(duì)主體對(duì)象進(jìn)行認(rèn)證
// 用戶登陸
// 設(shè)置用戶認(rèn)證的身份(principals)和憑證(credentials)
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 用戶認(rèn)證狀態(tài)
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);
// 用戶授權(quán)檢測(cè) 基于角色授權(quán)
// 是否有某一個(gè)角色
System.out.println("用戶是否擁有一個(gè)角色:" + subject.hasRole("role1"));
// 是否有多個(gè)角色
System.out.println("用戶是否擁有多個(gè)角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
// subject.checkRole("role1");
// subject.checkRoles(Arrays.asList("role1", "role2"));
// 授權(quán)檢測(cè)顺呕,失敗則拋出異常
// subject.checkRole("role22");
// 基于資源授權(quán)
System.out.println("是否擁有某一個(gè)權(quán)限:" + subject.isPermitted("user:delete"));
System.out.println("是否擁有多個(gè)權(quán)限:" + subject.isPermittedAll("user:create:1", "user:delete"));
//檢查權(quán)限
subject.checkPermission("sys:user:delete");
subject.checkPermissions("user:create:1","user:delete");
}
基于角色的授權(quán)
// 用戶授權(quán)檢測(cè) 基于角色授權(quán)
// 是否有某一個(gè)角色
System.out.println("用戶是否擁有一個(gè)角色:" + subject.hasRole("role1"));
// 是否有多個(gè)角色
System.out.println("用戶是否擁有多個(gè)角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
對(duì)應(yīng)的check方法:
subject.checkRole("role1");
subject.checkRoles(Arrays.asList("role1", "role2"));
上邊check方法如果授權(quán)失敗則拋出異常:
org.apache.shiro.authz.UnauthorizedException: Subject does not have role […..]
基于資源授權(quán)
// 基于資源授權(quán)
System.out.println("是否擁有某一個(gè)權(quán)限:" + subject.isPermitted("user:delete"));
System.out.println("是否擁有多個(gè)權(quán)限:" + subject.isPermittedAll("user:create:1", "user:delete"));
對(duì)應(yīng)的check方法:
subject.checkPermission("sys:user:delete");
subject.checkPermissions("user:create:1","user:delete");
上邊check方法如果授權(quán)失敗則拋出異常:
org.apache.shiro.authz.UnauthorizedException: Subject does not have permission [….]
自定義realm
與上邊認(rèn)證自定義realm一樣枫攀,大部分情況是要從數(shù)據(jù)庫(kù)獲取權(quán)限數(shù)據(jù),這里直接實(shí)現(xiàn)基于資源的授權(quán)株茶。
realm代碼
在認(rèn)證章節(jié)寫(xiě)的自定義realm類中完善doGetAuthorizationInfo方法来涨,此方法需要完成:根據(jù)用戶身份信息從數(shù)據(jù)庫(kù)查詢權(quán)限字符串,由shiro進(jìn)行授權(quán)
// 授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 獲取身份信息
String username = (String) principals.getPrimaryPrincipal();
// 根據(jù)身份信息從數(shù)據(jù)庫(kù)中查詢權(quán)限數(shù)據(jù)
//....這里使用靜態(tài)數(shù)據(jù)模擬
List<String> permissions = new ArrayList<String>();
permissions.add("user:create");
permissions.add("user.delete");
//將權(quán)限信息封閉為AuthorizationInfo
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for(String permission:permissions){
simpleAuthorizationInfo.addStringPermission(permission);
}
return simpleAuthorizationInfo;
}
shiro-realm.ini
ini配置文件還使用認(rèn)證階段使用的启盛,不用改變蹦掐。
測(cè)試代碼
同上邊的授權(quán)測(cè)試代碼技羔,注意修改ini地址為shiro-realm.ini。
授權(quán)執(zhí)行流程
1卧抗、 執(zhí)行subject.isPermitted(“user:create”)
2藤滥、 securityManager通過(guò)ModularRealmAuthorizer進(jìn)行授權(quán)
3、 ModularRealmAuthorizer調(diào)用realm獲取權(quán)限信息
4社裆、 ModularRealmAuthorizer再通過(guò)permissionResolver解析權(quán)限字符串拙绊,校驗(yàn)是否匹配
shiro與項(xiàng)目集成開(kāi)發(fā)
- shiro與spring web項(xiàng)目整合
shiro與springweb項(xiàng)目整合在“基于url攔截實(shí)現(xiàn)的工程”基礎(chǔ)上整合,基于url攔截實(shí)現(xiàn)的工程的技術(shù)架構(gòu)是springmvc+mybatis泳秀,整合注意兩點(diǎn):shiro與springweb項(xiàng)目整合在“基于url攔截實(shí)現(xiàn)的工程”基礎(chǔ)上整合标沪,基于url攔截實(shí)現(xiàn)的工程的技術(shù)架構(gòu)是springmvc+mybatis,整合注意兩點(diǎn):
- shiro與spring整合
- 加入shiro對(duì)web應(yīng)用的支持
取消原springmvc認(rèn)證和授權(quán)攔截器
去掉springmvc.xml中配置的LoginInterceptor和PermissionInterceptor攔截器嗜傅。
web.xml添加shiro Filter
<!--shiro-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設(shè)置為true則表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
applicationContext-shiro.xml
<!-- Shiro 的Web過(guò)濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- 如果沒(méi)有認(rèn)證將要跳轉(zhuǎn)的登陸地址金句,http可訪問(wèn)的url,如果不在表單認(rèn)證過(guò)慮器FormAuthenticationFilter中指定此地址就為身份認(rèn)證地址 -->
<property name="loginUrl" value="/login.action" />
<!-- 沒(méi)有權(quán)限跳轉(zhuǎn)的地址 -->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- shiro攔截器配置 -->
<property name="filters">
<map>
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- 必須通過(guò)身份認(rèn)證方可訪問(wèn)磺陡,身份認(rèn) 證的url必須和過(guò)慮器中指定的loginUrl一致 -->
/loginsubmit.action = authc
<!-- 退出攔截趴梢,請(qǐng)求logout.action執(zhí)行退出操作 -->
/logout.action = logout
<!-- 無(wú)權(quán)訪問(wèn)頁(yè)面 -->
/refuse.jsp = anon
<!-- roles[XX]表示有XX角色才可訪問(wèn) -->
/item/list.action = roles[item],authc
/js/** anon
/images/** anon
/styles/** anon
<!-- user表示身份認(rèn)證通過(guò)或通過(guò)記住我認(rèn)證通過(guò)的可以訪問(wèn) -->
/** = user
<!-- /**放在最下邊,如果一個(gè)url有多個(gè)過(guò)慮器則多個(gè)過(guò)慮器中間用逗號(hào)分隔币他,如:/** = user,roles[admin] -->
</value>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
</bean>
<!-- 自定義 realm -->
<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
</bean>
<!-- 基于Form表單的身份驗(yàn)證過(guò)濾器坞靶,不配置將也會(huì)注冊(cè)此過(guò)慮器,表單中的用戶賬號(hào)蝴悉、密碼及l(fā)oginurl將采用默認(rèn)值彰阴,建議配置 -->
<bean id="formAuthenticationFilter"
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<!-- 表單中賬號(hào)的input名稱 -->
<property name="usernameParam" value="usercode" />
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password" />
<!-- <property name="rememberMeParam" value="rememberMe"/> -->
<!-- loginurl:用戶登陸地址,此地址是可以http訪問(wèn)的url地址 -->
<property name="loginUrl" value="/loginsubmit.action" />
</bean>
使用shiro注解授權(quán)
在springmvc.xml中配置shiro注解支持拍冠,可在controller方法中使用shiro注解配置權(quán)限:
<!-- 開(kāi)啟aop尿这,對(duì)類代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 開(kāi)啟shiro注解支持 -->
<bean
class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
修改Controller代碼,在方法上添加授權(quán)注解庆杜,如下:
// 查詢列表
@RequestMapping("/queryItem")
@RequiresPermissions("item:query")
public ModelAndView queryItem() throws Exception {
上邊代碼@RequiresPermissions("item:query")表示必須擁有“item:query”權(quán)限方可執(zhí)行射众。
- 自定義realm
此realm先不從數(shù)據(jù)庫(kù)查詢權(quán)限數(shù)據(jù),當(dāng)前需要先將shiro整合完成晃财,在上邊章節(jié)定義的realm基礎(chǔ)上修改叨橱。
public class CustomRealm1 extends AuthorizingRealm {
@Autowired
private SysService sysService;
@Override
public String getName() {
return "customRealm";
}
// 支持什么類型的token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
// 認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// 從token中 獲取用戶身份信息
String username = (String) token.getPrincipal();
// 拿username從數(shù)據(jù)庫(kù)中查詢
// ....
// 如果查詢不到則返回null
if (!username.equals("zhang")) {// 這里模擬查詢不到
return null;
}
// 獲取從數(shù)據(jù)庫(kù)查詢出來(lái)的用戶密碼
String password = "123";// 這里使用靜態(tài)數(shù)據(jù)模擬。断盛。
// 根據(jù)用戶id從數(shù)據(jù)庫(kù)取出菜單
//...先用靜態(tài)數(shù)據(jù)
List<SysPermission> menus = new ArrayList<SysPermission>();;
SysPermission sysPermission_1 = new SysPermission();
sysPermission_1.setName("商品管理");
sysPermission_1.setUrl("/item/queryItem.action");
SysPermission sysPermission_2 = new SysPermission();
sysPermission_2.setName("用戶管理");
sysPermission_2.setUrl("/user/query.action");
menus.add(sysPermission_1);
menus.add(sysPermission_2);
// 構(gòu)建用戶身體份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(username);
activeUser.setUsername(username);
activeUser.setUsercode(username);
activeUser.setMenus(menus);
// 返回認(rèn)證信息由父類AuthenticatingRealm進(jìn)行認(rèn)證
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password, getName());
return simpleAuthenticationInfo;
}
// 授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 獲取身份信息
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
//用戶id
String userid = activeUser.getUserid();
// 根據(jù)用戶id從數(shù)據(jù)庫(kù)中查詢權(quán)限數(shù)據(jù)
// ....這里使用靜態(tài)數(shù)據(jù)模擬
List<String> permissions = new ArrayList<String>();
permissions.add("item:query");
permissions.add("item:update");
// 將權(quán)限信息封閉為AuthorizationInfo
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (String permission : permissions) {
simpleAuthorizationInfo.addStringPermission(permission);
}
return simpleAuthorizationInfo;
}
}
登錄
//用戶登陸頁(yè)面
@RequestMapping("/login")
public String login()throws Exception{
return "login";
}
// 用戶登陸提交
@RequestMapping("/loginsubmit")
public String loginsubmit(Model model, HttpServletRequest request)
throws Exception {
// shiro在認(rèn)證過(guò)程中出現(xiàn)錯(cuò)誤后將異常類路徑通過(guò)request返回
String exceptionClassName = (String) request
.getAttribute("shiroLoginFailure");
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new CustomException("賬號(hào)不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用戶名/密碼錯(cuò)誤");
} else{
throw new Exception();//最終在異常處理器生成未知錯(cuò)誤
}
}
首頁(yè)
由于session由shiro管理罗洗,需要修改首頁(yè)的controller方法:
//系統(tǒng)首頁(yè)
@RequestMapping("/first")
public String first(Model model)throws Exception{
//主體
Subject subject = SecurityUtils.getSubject();
//身份
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
model.addAttribute("activeUser", activeUser);
return "/first";
}
退出
由于使用shiro的sessionManager,不用開(kāi)發(fā)退出功能钢猛,使用shiro的logout攔截器即可伙菜。
<!-- 退出攔截,請(qǐng)求logout.action執(zhí)行退出操作 -->
/logout.action = logout
無(wú)權(quán)限r(nóng)efuse.jsp
當(dāng)用戶無(wú)操作權(quán)限命迈,shiro將跳轉(zhuǎn)到refuse.jsp頁(yè)面贩绕。
參考:applicationContext-shiro.xml
- realm連接數(shù)據(jù)庫(kù)
- 添加憑證匹配器
- 添加憑證匹配器實(shí)現(xiàn)md5加密校驗(yàn)火的。
修改applicationContext-shiro.xml:
<!-- 憑證匹配器 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
<!-- 自定義 realm -->
<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
realm代碼
修改realm代碼從數(shù)據(jù)庫(kù)中查詢用戶身份信息和權(quán)限信息,將sysService注入realm丧叽。
public class CustomRealm1 extends AuthorizingRealm {
@Autowired
private SysService sysService;
@Override
public String getName() {
return "customRealm";
}
// 支持什么類型的token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// 從token中獲取用戶身份
String usercode = (String) token.getPrincipal();
SysUser sysUser = null;
try {
sysUser = sysService.findSysuserByUsercode(usercode);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 如果賬號(hào)不存在
if (sysUser == null) {
throw new UnknownAccountException("賬號(hào)找不到");
}
// 根據(jù)用戶id取出菜單
List<SysPermission> menus = null;
try {
menus = sysService.findMenuList(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 用戶密碼
String password = sysUser.getPassword();
//鹽
String salt = sysUser.getSalt();
// 構(gòu)建用戶身體份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsername(sysUser.getUsername());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setMenus(menus);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password, ByteSource.Util.bytes(salt),getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//身份信息
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
//用戶id
String userid = activeUser.getUserid();
//獲取用戶權(quán)限
List<SysPermission> permissions = null;
try {
permissions = sysService.findSysPermissionList(userid);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//構(gòu)建shiro授權(quán)信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for(SysPermission sysPermission:permissions){
simpleAuthorizationInfo.addStringPermission(sysPermission.getPercode());
}
return simpleAuthorizationInfo;
}
}
緩存
shiro每個(gè)授權(quán)都會(huì)通過(guò)realm獲取權(quán)限信息卫玖,為了提高訪問(wèn)速度需要添加緩存,第一次從realm中讀取權(quán)限數(shù)據(jù)踊淳,之后不再讀取,這里Shiro和Ehcache整合陕靠。
- 添加Ehcache的依賴
在applicationContext-shiro.xml中配置緩存管理器迂尝。
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
</bean>
- session管理
在applicationContext-shiro.xml中配置sessionManager:
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 會(huì)話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效時(shí)長(zhǎng),單位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 刪除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
- 驗(yàn)證碼
需要在驗(yàn)證賬號(hào)和名稱之前校驗(yàn)驗(yàn)證碼-------》自定義FormAuthenticationFilter
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response, Object mappedValue) throws Exception {
// 校驗(yàn)驗(yàn)證碼
// 從session獲取正確的驗(yàn)證碼
HttpSession session = ((HttpServletRequest)request).getSession();
//頁(yè)面輸入的驗(yàn)證碼
String randomcode = request.getParameter("randomcode");
//從session中取出驗(yàn)證碼
String validateCode = (String) session.getAttribute("validateCode");
if (!randomcode.equals(validateCode)) {
// randomCodeError表示驗(yàn)證碼錯(cuò)誤
request.setAttribute("shiroLoginFailure", "randomCodeError");
//拒絕訪問(wèn)剪芥,不再校驗(yàn)賬號(hào)和密碼
return true;
}
return super.onAccessDenied(request, response, mappedValue);
}
}
- 修改FormAuthenticationFilter配置
修改applicationContext-shiro.xml中對(duì)FormAuthenticationFilter的配置垄开。
<bean id="formAuthenticationFilter"
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
改為
<bean id="formAuthenticationFilter"
class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
- 登陸頁(yè)面
添加驗(yàn)證碼:
<TR>
<TD>驗(yàn)證碼:</TD>
<TD><input id="randomcode" name="randomcode" size="8" />
<img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align='absMiddle' />
<a href=javascript:randomcode_refresh()>刷新</a></TD>
</TR>
-
配置validatecode.jsp匿名訪問(wèn)
修改applicationContext-shiro.xml:
validatecode.jsp.png - 記住我
用戶登陸選擇“自動(dòng)登陸”本次登陸成功會(huì)向cookie寫(xiě)身份信息,下次登陸從cookie中取出身份信息實(shí)現(xiàn)自動(dòng)登陸税肪。注意:用戶身份實(shí)現(xiàn)java.io.Serializable接口,向cookie記錄身份信息需要用戶身份信息對(duì)象實(shí)現(xiàn)序列化接口溉躲。
配置
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="cacheManager"/>
<!-- 記住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- rememberMeManager管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- 記住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<!-- 記住我cookie生效時(shí)間30天 -->
<property name="maxAge" value="2592000" />
</bean>
修改formAuthenticationFitler添加頁(yè)面中“記住我checkbox”的input名稱:
<bean id="formAuthenticationFilter"
class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
<!-- 表單中賬號(hào)的input名稱 -->
<property name="usernameParam" value="usercode" />
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password" />
<property name="rememberMeParam" value="rememberMe"/>
<!-- loginurl:用戶登陸地址,此地址是可以http訪問(wèn)的url地址 -->
<property name="loginUrl" value="/loginsubmit.action" />
</bean>
- 登陸頁(yè)面
在login.jsp中添加“記住我”checkbox益兄。
<TR>
<TD></TD>
<TD>
<input type="checkbox" name="rememberMe" />自動(dòng)登陸
</TD>
</TR>
附錄:
shiro過(guò)慮器
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
logout
org.apache.shiro.web.filter.authc.LogoutFilter
anon :例子 /admins/=anon 沒(méi)有參數(shù)锻梳,表示可以匿名使用。
authc :例如 /admins/user/=authc 表示需要認(rèn)證(登錄)才能使用净捅,沒(méi)有參數(shù)
roles :例子 /admins/user/=roles[admin] ,參數(shù)可以寫(xiě)多個(gè)疑枯,多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割蛔六,當(dāng)有多個(gè)參數(shù)時(shí)荆永,如admins/user/=roles["admin,guest"] ,每個(gè)參數(shù)通過(guò)才算通過(guò),相當(dāng)于 hasAllRoles() 方法国章。
perms :例子 /admins/user/=perms[user:add:] 參數(shù)可以寫(xiě)多個(gè)具钥,多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割液兽,例如 /admins/user/=perms["user:add:,user:modify:"] 骂删,當(dāng)有多個(gè)參數(shù)時(shí)必須每個(gè)參數(shù)都通過(guò)才通過(guò),想當(dāng)于isPermitedAll()方法抵碟。
rest :例子 /admins/user/=rest[user] ,根據(jù)請(qǐng)求的方法桃漾,相當(dāng)于 /admins/user/=perms[user:method] ,其中method為post,get拟逮,delete等撬统。
por t:例子 /admins/user/=port[8081] ,當(dāng)請(qǐng)求的url的端口不是8081是跳轉(zhuǎn)到 schemal://serverName:8081?queryString ,其中schmal是協(xié)議http或https等,serverName是你訪問(wèn)的host,8081是url配置里port的端口敦迄,queryString
是你訪問(wèn)的url里的恋追?后面的參數(shù)凭迹。
authcBasic :例如 /admins/user/=authcBasic 沒(méi)有參數(shù)表示httpBasic認(rèn)證
ssl: 例子 /admins/user/=ssl 沒(méi)有參數(shù),表示安全的url請(qǐng)求苦囱,協(xié)議為https
user :例如 /admins/user/*=user 沒(méi)有參數(shù)表示必須存在用戶嗅绸,當(dāng)?shù)侨氩僮鲿r(shí)不做檢查
注:
anon,authcBasic撕彤,auchc鱼鸠,user是認(rèn)證過(guò)濾器,
perms羹铅,roles蚀狰,ssl,rest职员,port是授權(quán)過(guò)濾器
- shiro的jsp標(biāo)簽
Jsp頁(yè)面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
標(biāo)簽名稱 標(biāo)簽條件(均是顯示標(biāo)簽內(nèi)容)
<shiro:authenticated> 登錄之后
<shiro:notAuthenticated> 不在登錄狀態(tài)時(shí)
<shiro:guest> 用戶在沒(méi)有RememberMe時(shí)
<shiro:user> 用戶在RememberMe時(shí)
<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色時(shí)
<shiro:hasRole name="abc"> 擁有角色abc
<shiro:lacksRole name="abc"> 沒(méi)有角色abc
<shiro:hasPermission name="abc"> 擁有權(quán)限資源abc
<shiro:lacksPermission name="abc"> 沒(méi)有abc權(quán)限資源
<shiro:principal> 顯示用戶身份名稱
<shiro:principal property="username"/> 顯示用戶身份中的屬性值