【前言】
Apache Shiro是Java的一個(gè)安全框架。主要用于權(quán)限控制,簡(jiǎn)單易用妇斤。之前單看shiro始終理會(huì)不了它的思想,后來在網(wǎng)上發(fā)現(xiàn)某智的一個(gè)bos項(xiàng)目中運(yùn)用到了shiro,果斷學(xué)習(xí)站超,看完后略有心得荸恕,以記之。
【項(xiàng)目簡(jiǎn)介】
該bos項(xiàng)目主要是一個(gè)后臺(tái)管理項(xiàng)目死相,采用傳統(tǒng)ssh技術(shù)架構(gòu)融求,使用spring與shiro進(jìn)行整合做權(quán)限攔截,以下是項(xiàng)目中權(quán)限部分的筆記算撮。
首先生宛,我們從外部來看Shiro吧,即從應(yīng)用程序角度的來觀察如何使用Shiro完成工作
Subject:主體肮柜,代表了當(dāng)前“用戶”
SecurityManager:安全管理器陷舅;即所有與安全有關(guān)的操作都會(huì)與SecurityManager交互
Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶审洞、角色莱睁、權(quán)限)
用戶主體訪問系統(tǒng),由安全管理器進(jìn)行權(quán)限驗(yàn)證芒澜,安全管理器從Realm域中獲取到該用戶的角色權(quán)限仰剿,并作出相應(yīng)的權(quán)限反饋,Realm域就是一個(gè)Dao痴晦,主要獲取用戶的角色權(quán)限數(shù)據(jù)并交給安全管理器南吮。所以整個(gè)流程中,安全管理器是核心角色誊酌。
【1】web.xml中配置shiro的過濾器
shiro的過濾器就類似于struts2的核心過濾器一般
<!-- shiro過濾器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:這里filterName的值可以隨便起部凑,無要求。但是在spring注入時(shí)要保持一致
【2】將shiro過濾器注入spring
<!-- 配置工廠bean术辐,用于創(chuàng)建shiro框架用到過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入當(dāng)前系統(tǒng)的登錄頁面 -->
<property name="loginUrl" value="/login.jsp"></property>
<!-- 注入成功頁面 -->
<property name="successUrl" value="/index.jsp"></property>
<!-- 注入權(quán)限不足頁面 -->
<property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
<!-- 注入url攔截規(guī)則 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/images/** = anon
/js/** = anon
/login.jsp* = anon
/validatecode.jsp* = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff"]<!-- roles角色集,perms權(quán)限集 -->
/* = authc
</value>
<!--
/page_base_staff.action = roles["staff"]//要訪問此action必須有staff這個(gè)角色
/page_base_staff.action = perms["staff"]//要訪問此action必須有staff這個(gè)權(quán)限
-->
</property>
</bean>
注意:以上屬性均以set注入砚尽,查看ShiroFilterFactoryBean源碼便知,業(yè)務(wù)需要配哪個(gè)屬性就配哪個(gè)辉词,非必要配置必孤,這里頁面以/開頭均在webroot目錄下。
這里bean 的id屬性要與web.xml中shiro的過濾器名稱一致
url攔截規(guī)則:一般圖片瑞躺、JS敷搪、CSS樣式不攔截,直接設(shè)置anon角色權(quán)限即可(/css/** = anon)
具體url攔截:
/page_base_staff.action = perms["staff"]//訪問此action需要staff權(quán)限
/page_base_staff.action =roles["staff"]//訪問此action需要staff角色(角色是權(quán)限的集合)
要攔截的路徑:/* = authc
【3】編寫自定義Realm域并注入到spring中
自定義Realm需要繼承AuthorizingRealm幢哨,實(shí)現(xiàn)其認(rèn)證 授權(quán)方法
/**
* ClassName: BOSRealm
* @author lvfang
* @Desc: 自定義realm
* @date 2017-8-23
*/
public class BOSRealm extends AuthorizingRealm {
@Resource
private IUserDao userDao;
/**
* 認(rèn)證方法(是否有這個(gè)用戶)
*/
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("進(jìn)入認(rèn)證方法... ...");
UsernamePasswordToken upToken = (UsernamePasswordToken) token;//token令牌強(qiáng)轉(zhuǎn)
String username = upToken.getUsername();//從令牌中得到用戶名
//查詢用戶
User user = userDao.findUserByUsername(username);
if(user == null){
//用戶名不存在
return null;
}else{
//用戶名存在
String password = user.getPassword();
// 創(chuàng)建簡(jiǎn)單認(rèn)證信息對(duì)象
/***
* 參數(shù)一:簽名赡勘,程序可以在任意位置獲取當(dāng)前放入的對(duì)象
* 參數(shù)二:從數(shù)據(jù)庫(kù)中查詢出的密碼
* 參數(shù)三:當(dāng)前realm的名稱
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,this.getClass().getSimpleName());
//返回給安全管理器,由安全管理器負(fù)責(zé)比對(duì)數(shù)據(jù)庫(kù)中查詢出的密碼和頁面提交的密碼
return info;
}
}
/**
* 授權(quán)方法(這個(gè)用戶有什么權(quán)限)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("進(jìn)入授權(quán)方法... ...");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("staff");//為當(dāng)前用戶授予staff權(quán)限
info.addRole("staff");//為當(dāng)前用戶授予staff角色(角色是權(quán)限的集合)
//TODO 根據(jù)當(dāng)前登錄用戶查詢數(shù)據(jù)庫(kù)捞镰,獲取其對(duì)應(yīng)的權(quán)限數(shù)據(jù)
return info;
}
}
注入spring
<!-- 注冊(cè)自定義realm -->
<bean id="bosRealm" class="com.itheima.bos.shiro.BOSRealm"></bean>
【4】注入安全管理器闸与,并將realm注入給安全管理器
<!-- 注入 securityManager管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 將自定義realm注入給securityManager管理 -->
<property name="realm" ref="bosRealm"></property>
</bean>
【5】在login方法中做處理
/**
* 登陸方法(shiro版)
* @return
*/
public String login(){
//判斷驗(yàn)證碼
String code = (String) this.setSession("key");
//判斷驗(yàn)證碼是否正確
if(StringUtils.isNotBlank(checkcode)&& checkcode.equals(code)){
//獲取當(dāng)前用戶對(duì)象
Subject subject = SecurityUtils.getSubject();//目前為"未認(rèn)證狀態(tài)"
String password = model.getPassword();
password = MD5Utils.md5(password);
//構(gòu)造一個(gè)用戶名密碼令牌
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(),password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
//登陸失敗 設(shè)置提示信息毙替,跳轉(zhuǎn)登陸頁面
this.addActionError("用戶名不存在!");
return "login";
} catch (Exception e) {
e.printStackTrace();
//登陸失敗 設(shè)置提示信息践樱,跳轉(zhuǎn)登陸頁面
this.addActionError("用戶名或密碼錯(cuò)誤厂画!");
return "login";
}
//獲取認(rèn)證信息對(duì)象中存儲(chǔ)的User對(duì)象
User user = (User) subject.getPrincipal();
this.getSession().setAttribute("loginUser", user);//用戶存入session
return "home";
}else{
//登陸失敗,驗(yàn)證碼失敗 跳轉(zhuǎn)至登陸頁面
this.addActionError("驗(yàn)證碼錯(cuò)誤拷邢!");
return "login";
}
}
這里根據(jù)username和password構(gòu)造一個(gè)用戶名令牌袱院,當(dāng)subject主體去調(diào)用login()方法登陸時(shí),會(huì)執(zhí)行Realm域中的認(rèn)證和授權(quán)方法瞭稼。
【附加】
1:
shiro過濾器屬性含義
securityManager:這個(gè)屬性是必須的忽洛。
loginUrl :沒有登錄的用戶請(qǐng)求需要登錄的頁面時(shí)自動(dòng)跳轉(zhuǎn)到登錄頁面,不是必須的屬性环肘,不輸入地址的話會(huì)自動(dòng)尋找項(xiàng)目web項(xiàng)目的根目錄下的”/login.jsp”頁面欲虚。
successUrl :登錄成功默認(rèn)跳轉(zhuǎn)頁面,不配置則跳轉(zhuǎn)至”/”廷臼。如果登陸前點(diǎn)擊的一個(gè)需要登錄的頁面苍在,則在登錄自動(dòng)跳轉(zhuǎn)到那個(gè)需要登錄的頁面绝页。不跳轉(zhuǎn)到此荠商。
unauthorizedUrl :沒有權(quán)限默認(rèn)跳轉(zhuǎn)的頁面
2:
其權(quán)限過濾器及配置釋義
anon:例子/admins/**=anon 沒有參數(shù),表示可以匿名使用续誉。
authc:例如/admins/user/**=authc表示需要認(rèn)證(登錄)才能使用莱没,沒有參數(shù)
roles(角色):例子/admins/user/**=roles[admin],參數(shù)可以寫多個(gè),多個(gè)時(shí)必須加上引號(hào)酷鸦,并且參數(shù)之間用逗號(hào)分割饰躲,當(dāng)有多個(gè)參數(shù)時(shí),例如admins/user/**=roles["admin,guest"],每個(gè)參數(shù)通過才算通過臼隔,相當(dāng)于hasAllRoles()方法嘹裂。
perms(權(quán)限):例子/admins/user/**=perms[user:add:*],參數(shù)可以寫多個(gè),多個(gè)時(shí)必須加上引號(hào)摔握,并且參數(shù)之間用逗號(hào)分割寄狼,例如/admins/user/**=perms["user:add:*,user:modify:*"],當(dāng)有多個(gè)參數(shù)時(shí)必須每個(gè)參數(shù)都通過才通過氨淌,想當(dāng)于isPermitedAll()方法泊愧。
rest:例子/admins/user/**=rest[user],根據(jù)請(qǐng)求的方法,相當(dāng)于/admins/user/**=perms[user:method] ,其中method為post盛正,get删咱,delete等。
port:例子/admins/user/**=port[8081],當(dāng)請(qǐng)求的url的端口不是8081是跳轉(zhuǎn)到schemal://serverName:8081?queryString,其中schmal是協(xié)議http或https等豪筝,serverName是你訪問的host,8081是url配置里port的端口痰滋,queryString
是你訪問的url里的摘能?后面的參數(shù)。
authcBasic:例如/admins/user/**=authcBasic沒有參數(shù)表示httpBasic認(rèn)證
ssl:例子/admins/user/**=ssl沒有參數(shù)敲街,表示安全的url請(qǐng)求徊哑,協(xié)議為https
user:例如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當(dāng)?shù)侨氩僮鲿r(shí)不做檢查