【前言】
Apache Shiro是Java的一個(gè)安全框架。主要用于權(quán)限控制,簡(jiǎn)單易用。之前單看shiro始終理會(huì)不了它的思想,后來(lái)在網(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)限部分的筆記。
首先,我們從外部來(lái)看Shiro吧,即從應(yīng)用程序角度的來(lái)觀察如何使用Shiro完成工作

Subject:主體,代表了當(dāng)前“用戶(hù)”
SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會(huì)與SecurityManager交互
Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶(hù)、角色、權(quán)限)
用戶(hù)主體訪(fǎng)問(wèn)系統(tǒng),由安全管理器進(jìn)行權(quán)限驗(yàn)證,安全管理器從Realm域中獲取到該用戶(hù)的角色權(quán)限,并作出相應(yīng)的權(quán)限反饋,Realm域就是一個(gè)Dao,主要獲取用戶(hù)的角色權(quán)限數(shù)據(jù)并交給安全管理器。所以整個(gè)流程中,安全管理器是核心角色。
【1】web.xml中配置shiro的過(guò)濾器
shiro的過(guò)濾器就類(lèi)似于struts2的核心過(guò)濾器一般
<!-- shiro過(guò)濾器 -->
<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的值可以隨便起,無(wú)要求。但是在spring注入時(shí)要保持一致
【2】將shiro過(guò)濾器注入spring
<!-- 配置工廠(chǎng)bean,用于創(chuàng)建shiro框架用到過(guò)濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入當(dāng)前系統(tǒng)的登錄頁(yè)面 -->
<property name="loginUrl" value="/login.jsp"></property>
<!-- 注入成功頁(yè)面 -->
<property name="successUrl" value="/index.jsp"></property>
<!-- 注入權(quán)限不足頁(yè)面 -->
<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"]//要訪(fǎng)問(wèn)此action必須有staff這個(gè)角色
/page_base_staff.action = perms["staff"]//要訪(fǎng)問(wèn)此action必須有staff這個(gè)權(quán)限
-->
</property>
</bean>
注意:以上屬性均以set注入,查看ShiroFilterFactoryBean源碼便知,業(yè)務(wù)需要配哪個(gè)屬性就配哪個(gè),非必要配置,這里頁(yè)面以/開(kāi)頭均在webroot目錄下。
這里bean 的id屬性要與web.xml中shiro的過(guò)濾器名稱(chēng)一致
url攔截規(guī)則:一般圖片、JS、CSS樣式不攔截,直接設(shè)置anon角色權(quán)限即可(/css/** = anon)
具體url攔截:
/page_base_staff.action = perms["staff"]//訪(fǎng)問(wèn)此action需要staff權(quán)限
/page_base_staff.action =roles["staff"]//訪(fǎng)問(wèn)此action需要staff角色(角色是權(quán)限的集合)
要攔截的路徑:/* = authc
【3】編寫(xiě)自定義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è)用戶(hù))
*/
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();//從令牌中得到用戶(hù)名
//查詢(xún)用戶(hù)
User user = userDao.findUserByUsername(username);
if(user == null){
//用戶(hù)名不存在
return null;
}else{
//用戶(hù)名存在
String password = user.getPassword();
// 創(chuàng)建簡(jiǎn)單認(rèn)證信息對(duì)象
/***
* 參數(shù)一:簽名,程序可以在任意位置獲取當(dāng)前放入的對(duì)象
* 參數(shù)二:從數(shù)據(jù)庫(kù)中查詢(xún)出的密碼
* 參數(shù)三:當(dāng)前realm的名稱(chēng)
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,this.getClass().getSimpleName());
//返回給安全管理器,由安全管理器負(fù)責(zé)比對(duì)數(shù)據(jù)庫(kù)中查詢(xún)出的密碼和頁(yè)面提交的密碼
return info;
}
}
/**
* 授權(quán)方法(這個(gè)用戶(hù)有什么權(quán)限)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("進(jìn)入授權(quán)方法... ...");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("staff");//為當(dāng)前用戶(hù)授予staff權(quán)限
info.addRole("staff");//為當(dāng)前用戶(hù)授予staff角色(角色是權(quán)限的集合)
//TODO 根據(jù)當(dāng)前登錄用戶(hù)查詢(xún)數(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)前用戶(hù)對(duì)象
Subject subject = SecurityUtils.getSubject();//目前為"未認(rèn)證狀態(tài)"
String password = model.getPassword();
password = MD5Utils.md5(password);
//構(gòu)造一個(gè)用戶(hù)名密碼令牌
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(),password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
//登陸失敗 設(shè)置提示信息,跳轉(zhuǎn)登陸頁(yè)面
this.addActionError("用戶(hù)名不存在!");
return "login";
} catch (Exception e) {
e.printStackTrace();
//登陸失敗 設(shè)置提示信息,跳轉(zhuǎn)登陸頁(yè)面
this.addActionError("用戶(hù)名或密碼錯(cuò)誤!");
return "login";
}
//獲取認(rèn)證信息對(duì)象中存儲(chǔ)的User對(duì)象
User user = (User) subject.getPrincipal();
this.getSession().setAttribute("loginUser", user);//用戶(hù)存入session
return "home";
}else{
//登陸失敗,驗(yàn)證碼失敗 跳轉(zhuǎn)至登陸頁(yè)面
this.addActionError("驗(yàn)證碼錯(cuò)誤!");
return "login";
}
}
這里根據(jù)username和password構(gòu)造一個(gè)用戶(hù)名令牌,當(dāng)subject主體去調(diào)用login()方法登陸時(shí),會(huì)執(zhí)行Realm域中的認(rèn)證和授權(quán)方法。
【附加】
1:
shiro過(guò)濾器屬性含義
securityManager:這個(gè)屬性是必須的。
loginUrl :沒(méi)有登錄的用戶(hù)請(qǐng)求需要登錄的頁(yè)面時(shí)自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面,不是必須的屬性,不輸入地址的話(huà)會(huì)自動(dòng)尋找項(xiàng)目web項(xiàng)目的根目錄下的”/login.jsp”頁(yè)面。
successUrl :登錄成功默認(rèn)跳轉(zhuǎn)頁(yè)面,不配置則跳轉(zhuǎn)至”/”。如果登陸前點(diǎn)擊的一個(gè)需要登錄的頁(yè)面,則在登錄自動(dòng)跳轉(zhuǎn)到那個(gè)需要登錄的頁(yè)面。不跳轉(zhuǎn)到此。
unauthorizedUrl :沒(méi)有權(quán)限默認(rèn)跳轉(zhuǎn)的頁(yè)面
2:
其權(quán)限過(guò)濾器及配置釋義
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(權(quán)限):例子/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等。
port:例子/admins/user/**=port[8081],當(dāng)請(qǐng)求的url的端口不是8081是跳轉(zhuǎn)到schemal://serverName:8081?queryString,其中schmal是協(xié)議http或https等,serverName是你訪(fǎng)問(wèn)的host,8081是url配置里port的端口,queryString
是你訪(fǎng)問(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ù)表示必須存在用戶(hù),當(dāng)?shù)侨氩僮鲿r(shí)不做檢查