title: Shiro整合Web項(xiàng)目及整合后的開(kāi)發(fā)
tags: shiro
categories: shiro
將Shiro框架整合到新的web項(xiàng)目中很簡(jiǎn)單,就是在web項(xiàng)目中導(dǎo)入Shiro的相關(guān)jar包以及整合jar包即可完成整合(是不是很簡(jiǎn)單...哈哈就是這么簡(jiǎn)單)。難的就是整合了Shiro框架后的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā),關(guān)于這一點(diǎn),我將在下方通過(guò)一個(gè)demo演示用戶(hù)的登錄與退出及登錄后的權(quán)限管理帶你入門(mén)加入了Shiro框架后的web項(xiàng)目開(kāi)發(fā)。
寫(xiě)在前邊的話:shiro基礎(chǔ)知識(shí)的講解請(qǐng)看前面的兩篇文章。另外我在github上已經(jīng)放了一個(gè)整合了Spring+SpringMVC+Mybatis的web項(xiàng)目(就是關(guān)于商品的增、刪、改、查操作),接下來(lái)我要講解的就是如何在這個(gè)項(xiàng)目中整合進(jìn)我的Shiro框架,整合Shiro框架前的項(xiàng)目源碼請(qǐng)點(diǎn)擊這里前往我的github,并講解了整合了Shiro框架后的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā),整合了Shiro框架后的完整源碼請(qǐng)點(diǎn)擊這里前往我的github。
用于創(chuàng)建表的sql語(yǔ)句見(jiàn)github上src包下的sql包。
1.需求
在一個(gè)整合了Spring+SpringMVC+Mybatis三個(gè)框架的web項(xiàng)目中再整合進(jìn)Shiro框架,實(shí)現(xiàn)基于Shiro的權(quán)限管理機(jī)制。
2.導(dǎo)入jar包
在原先的項(xiàng)目基礎(chǔ)上只需導(dǎo)入三個(gè)jar包即可:1.shiro-spring.jar。2.shiro-web.jar。3.shiro-core.jar。jar包見(jiàn)我github上的源代碼。成功導(dǎo)入jar包,好,下一步,整合完畢。
項(xiàng)目相關(guān)jsp頁(yè)面請(qǐng)?jiān)趃ithub上自行下載,我們這里只進(jìn)行web后端功能的講解。接下來(lái)在原先的項(xiàng)目基礎(chǔ)上通過(guò)增加用戶(hù)登錄和退出的功能對(duì)用戶(hù)進(jìn)行權(quán)限管理來(lái)講解如何使用Shiro 進(jìn)行開(kāi)發(fā)。
3.在web.xml中配置shiro的filter
在web系統(tǒng)中,shiro也是通過(guò)filter進(jìn)行攔截的。filter攔截后將操作權(quán)交給Spring中配置的filterChain(過(guò)濾鏈兒),shiro提供了很多的filter。
在web.xml中配置shiro的filter,加入如下內(nèi)容:
<!--在這里配置shiro的filter-->
<!-- shiro過(guò)慮器,DelegatingFilterProxy通過(guò)代理模式將spring容器中的bean和filter關(guān)聯(lián)起來(lái) -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 設(shè)置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 設(shè)置spring容器filter的bean id,如果不設(shè)置則找與filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4.applicationContext-shiro.xml
在src包下的config包下創(chuàng)建applicationContext-shiro.xml,在applicationContext-shiro.xml中配置web.xml中fitler對(duì)應(yīng)spring容器中的bean以及SecurityManeger和自定義Realm的配置。內(nèi)容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!--web.xml中shiro的filter對(duì)應(yīng)的bean-->
<!-- Shiro 的Web過(guò)濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl認(rèn)證提交地址,如果沒(méi)有認(rèn)證將會(huì)請(qǐng)求此地址進(jìn)行認(rèn)證,請(qǐng)求此地址將由formAuthenticationFilter進(jìn)行表單認(rèn)證 -->
<property name="loginUrl" value="/login.action" />
<!--認(rèn)證成功統(tǒng)一跳轉(zhuǎn)到first.actio,建議不配置,不配置的話shiro認(rèn)證成功會(huì)自動(dòng)到上一個(gè)請(qǐng)求路徑-->
<property name="successUrl" value="/first.action"/>
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 過(guò)慮器鏈定義,從上向下順序執(zhí)行,一般將/**放在最下邊 -->
<property name="filterChainDefinitions">
<value>
<!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--/**=anon 表示所有的url都可以匿名訪問(wèn),anon是shiro中一個(gè)過(guò)濾器的簡(jiǎn)寫(xiě),關(guān)于shiro中的過(guò)濾器介紹見(jiàn)-->
/**=anon
</value>
</property>
</bean>
<!--securityManage-->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
</bean>
<!--自定義realm-->
<bean id="customRealm" class="shiro.CustomRealm">
</bean>
</beans>
在applicationContext-shiro.xml的配置文件中,我們對(duì)系統(tǒng)的任何資源進(jìn)行攔截,即通過(guò)/**=anon設(shè)置系統(tǒng)的任何資源都可以進(jìn)行匿名訪問(wèn)。運(yùn)行程序,在瀏覽器中輸入http://localhost:8080/Shiro/即可訪問(wèn)系統(tǒng),發(fā)現(xiàn)沒(méi)有任何攔截即可以正常訪問(wèn)系統(tǒng),因?yàn)閟hiro的過(guò)濾器沒(méi)有對(duì)系統(tǒng)任何資源進(jìn)行攔截,若想進(jìn)行攔截,可以在上述配置文件中的<value></value>標(biāo)簽之間加入相應(yīng)的攔截語(yǔ)句。下面就通過(guò)增加用戶(hù)的登錄實(shí)現(xiàn)通過(guò)Shiro的filter進(jìn)行認(rèn)證攔截的功能。即當(dāng)訪問(wèn)被shiro攔截的系統(tǒng)資源時(shí),系統(tǒng)會(huì)自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面提醒用戶(hù)需要經(jīng)過(guò)用戶(hù)登錄認(rèn)證后才能正常訪問(wèn)。
5.用Shiro實(shí)現(xiàn)登錄認(rèn)證
5.1原理
用戶(hù)登錄是在一個(gè)表單進(jìn)行的,所以這里我們需要通過(guò)shiro的一個(gè)表單過(guò)濾器(FormAuthenticationFilter)進(jìn)行實(shí)現(xiàn),原理如下:
用戶(hù)沒(méi)有認(rèn)證時(shí),請(qǐng)求loginurl進(jìn)行認(rèn)證,輸入用戶(hù)名和密碼點(diǎn)擊登錄時(shí)將用戶(hù)身份和用戶(hù)密碼提交數(shù)據(jù)到loginurl,然后FormAuthenticationFilter進(jìn)行攔截取出request中的username和password(FormAuthenticationFilter源碼中將username和password兩個(gè)參數(shù)名稱(chēng)寫(xiě)死了,而我們今后是可以將這兩個(gè)參數(shù)名稱(chēng)寫(xiě)在配置文件中的),然后FormAuthenticationFilter會(huì)調(diào)用realm傳入一個(gè)token(將username和password傳入到token中),realm認(rèn)證時(shí)根據(jù)username在數(shù)據(jù)庫(kù)中查詢(xún)用戶(hù)信息(將在數(shù)據(jù)庫(kù)中查詢(xún)到的信息保存在在Activeuser.java對(duì)象中,包括 userid、usercode、username、menus),然后返回一個(gè)authenticationInfo。如果查詢(xún)不到,realm就返回null,同時(shí)FormAuthenticationFilter會(huì)向request域中填充一個(gè)參數(shù)(記錄了異常信息)。
5.2登錄的代碼實(shí)現(xiàn)
可想而知該代碼在控制器Controller中實(shí)現(xiàn),創(chuàng)建一個(gè)LoginController.java,代碼如下:
@Controller
public class LoginController
{
@RequestMapping("/login")
public String login(HttpServletRequest request) throws Exception
{
//如果登錄失敗從request中獲取認(rèn)證異常信息,shiroLoginFailure就是shiro異常類(lèi)的全限定名
String exceptionClassName= (String) request.getAttribute("shiroLoginFailure");
//根據(jù)shiro返回的異常類(lèi)路徑判斷,拋出指定異常信息
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最終會(huì)拋給異常處理器
throw new CustomException("賬號(hào)不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用戶(hù)名/密碼錯(cuò)誤");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("驗(yàn)證碼錯(cuò)誤");
} else{
throw new Exception();//最終在異常處理器生成未知錯(cuò)誤
}
}
}
5.3配置認(rèn)證攔截過(guò)濾器
在applicationContext.xml的<bean>標(biāo)簽中加入如下標(biāo)簽配置:
<!-- loginUrl認(rèn)證提交地址,如果沒(méi)有認(rèn)證將會(huì)請(qǐng)求此地址進(jìn)行認(rèn)證,請(qǐng)求此地址將由formAuthenticationFilter進(jìn)行表單認(rèn)證 -->
<property name="loginUrl" value="/login.action" />
并在<value>標(biāo)簽之間加入相應(yīng)的攔截語(yǔ)句:
<!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
可以匿名訪問(wèn)的頁(yè)面我們以后再配置
運(yùn)行服務(wù)器,訪問(wèn)系統(tǒng)首頁(yè)發(fā)現(xiàn)系統(tǒng)會(huì)對(duì)我們?cè)L問(wèn)的資源進(jìn)行攔截并退回到登錄頁(yè)面,但是這里會(huì)有個(gè)問(wèn)題發(fā)現(xiàn)登錄頁(yè)面的靜態(tài)資源也被攔截了,所以我們應(yīng)在<value>標(biāo)簽之間加入對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)的設(shè)置:
<!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
/logout.action=logout
<!--商品查詢(xún)需要商品查詢(xún)權(quán)限-->
/items/queryItems.action=perms[item:query]
/items/editItems.action=perms[item:edit]
<!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
可以匿名訪問(wèn)的頁(yè)面我們以后再配置
然后運(yùn)行程序,訪問(wèn)系統(tǒng)資源時(shí)系統(tǒng)發(fā)現(xiàn)用戶(hù)信息沒(méi)有得到認(rèn)證所以會(huì)退回到登錄頁(yè)面讓你進(jìn)行登錄,你只有輸入了密碼為111111后才能成功完成登錄,因?yàn)槲覀冊(cè)谧远xCustomRealm.java文件中只是模擬從數(shù)據(jù)庫(kù)中查到的數(shù)據(jù)(我們?cè)O(shè)置查到的密碼為111111)。登錄成功后便可進(jìn)行系統(tǒng)的訪問(wèn)了,但是登錄成功后只能訪問(wèn)系統(tǒng)的首頁(yè),因?yàn)槲覀冞€沒(méi)有對(duì)該用戶(hù)進(jìn)行權(quán)限分配指定該用戶(hù)可以對(duì)系統(tǒng)的哪些資源進(jìn)行操作了,所以這里當(dāng)然只能訪問(wèn)系統(tǒng)首頁(yè)。當(dāng)然運(yùn)行程序之前你得完成自定義CustomRealm的代碼,我們采用前篇文章的自定義CustomRealm的內(nèi)容,如下:
public class CustomRealm extends AuthorizingRealm
{
//注入service
@Autowired
private SysService sysService;
//設(shè)置realm的名稱(chēng)
@Override
public void setName(String name) {
super.setName("customRealm");
}
//用于認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token是用戶(hù)輸入的
//第一步:叢token中取出身份信息
String userCode= (String) token.getPrincipal();
//第二步:根據(jù)用戶(hù)輸入的userCode叢數(shù)據(jù)庫(kù)查詢(xún)
//模擬叢數(shù)據(jù)庫(kù)查詢(xún)到的密碼
String password="111111";
//如果查不到返回null,
//如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo
///將activeUser設(shè)置到simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(userCode,password,this.getName());
return simpleAuthenticationInfo;
}
//用于授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
這樣便完成用戶(hù)的認(rèn)證功能,接下來(lái)是退出功能。
6.退出
退出功能就是當(dāng)用戶(hù)點(diǎn)擊退出按鈕時(shí)清楚保存在session中信息,這個(gè)功能不用我們實(shí)現(xiàn),交給Shiro的LogoutFilter過(guò)濾器即可實(shí)現(xiàn):當(dāng)我們?cè)L問(wèn)一個(gè)退出的url時(shí),由LogoutFilter攔截住,然后清楚session。
6.1配置退出過(guò)濾器
在applicationContext-shiro.xml的<value>標(biāo)簽中加入如下內(nèi)容:
<!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
/logout.action=logout
即完成清楚session即退出系統(tǒng)的功能。
7.實(shí)現(xiàn)用戶(hù)成功登錄后將認(rèn)證信息顯示在頁(yè)面上
需求:1.認(rèn)證后用戶(hù)菜單在首頁(yè)顯示。2.認(rèn)證后用戶(hù)的信息(例如用戶(hù)名)在頁(yè)頭顯示。
7.1修改自定義Realm設(shè)置完整的認(rèn)證信息
先前我們通過(guò)realm在數(shù)據(jù)庫(kù)中通過(guò)用戶(hù)名查詢(xún)到的用戶(hù)信息只有密碼,而現(xiàn)在我們需要查詢(xún)到的數(shù)據(jù)包括用戶(hù)可以操作的用戶(hù)菜單、usercode用戶(hù)id、username用戶(hù)名等。
我們先將這些信息用靜態(tài)代碼實(shí)現(xiàn)(即仍然沒(méi)有涉及到數(shù)據(jù)庫(kù)的查詢(xún)):
//模擬叢數(shù)據(jù)庫(kù)查詢(xún)到的密碼
String password="111111";
//activeUser就是用戶(hù)的身份信息
ActiveUser activeUser=new ActiveUser();
activeUser.setUserid("zhangsan");
activeUser.setUsercode("zhangsan");
activeUser.setUsername("張三");
//根據(jù)用戶(hù)id取出菜單
//通過(guò)service取出菜單
List<SysPermission> menus=null;
try {
menus=sysService.findMenuListByUserId("zhangsan");
} catch (Exception e) {
e.printStackTrace();
}
//將用戶(hù)菜單設(shè)置到activeUser
activeUser.setMenus(menus);
//如果查不到返回null,
//如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo
///將activeUser設(shè)置到simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(activeUser,password,this.getName());
然后修改first.action(在控制器FirstAction.java中實(shí)現(xiàn)該方法,當(dāng)訪問(wèn)系統(tǒng)主頁(yè)index.jsp時(shí)該index.jsp頁(yè)面中設(shè)置將頁(yè)面進(jìn)行跳轉(zhuǎn)到first.action)將認(rèn)證信息在頁(yè)面中進(jìn)行顯示:
@Controller
public class FirstAction {
//系統(tǒng)首頁(yè)
@RequestMapping("/first")
public String first(Model model)throws Exception{
//從shiro的session中取出activeUser
Subject subject= SecurityUtils.getSubject();
//取出身份信息
ActiveUser activeUser= (ActiveUser) subject.getPrincipal();
//通過(guò)model傳到頁(yè)面
model.addAttribute("activeUser",activeUser);
return "/first";
}
//歡迎頁(yè)面
@RequestMapping("/welcome")
public String welcome(Model model)throws Exception{
return "/welcome";
}
}
運(yùn)行程序,登錄該系統(tǒng)后發(fā)現(xiàn)出現(xiàn)商品管理的菜單,我們?nèi)匀粵](méi)有對(duì)商品進(jìn)行操作的權(quán)限,所以接下來(lái)要講解通過(guò)Shiro如何對(duì)用戶(hù)進(jìn)行授權(quán)操作。仍然在自定義Realm中模擬從數(shù)據(jù)庫(kù)查詢(xún)到的用戶(hù)權(quán)限。
8.授權(quán)過(guò)濾器的測(cè)試
在Shiro中使用PermissionsAuthorizationFilter對(duì)用戶(hù)進(jìn)行授權(quán),首先在applicationContext-shiro.xml中進(jìn)行配置,加入如下內(nèi)容:
<!--商品查詢(xún)需要商品查詢(xún)權(quán)限-->
/items/queryItems.action=perms[item:query]
<!--商品修改需要商品修改權(quán)限-->
/items/editItems.action=perms[item:edit]
通過(guò)上述配置,用戶(hù)在認(rèn)證通過(guò)后請(qǐng)求/items/queryItems.action的資源時(shí)會(huì)被PermissionsAuthorizationFilter攔截,發(fā)現(xiàn)需要“item:query”權(quán)限,然后PermissionsAuthorizationFilter調(diào)用realm中的doGetAuthorizationInfo獲取數(shù)據(jù)庫(kù)中正確的權(quán)限,對(duì)二者進(jìn)行對(duì)比,如果“item:query”在realm返回的權(quán)限列表中,授權(quán)通過(guò)。如果不通過(guò),則授權(quán)失敗,跳轉(zhuǎn)到refuse.jsp頁(yè)面。所以我們還需要在applicationContext-shiro.xml進(jìn)行授權(quán)失敗后跳轉(zhuǎn)到的頁(yè)面配置:
<property name="unauthorizedUrl" value="/refuse.jsp" />
在自定義Realm中的授權(quán)方法中加入如下內(nèi)容模擬從數(shù)據(jù)庫(kù)中查到的用戶(hù)權(quán)限,內(nèi)容如下:
//用于授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//從principals獲取主身份信息
//將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類(lèi)型(在上邊的goGetAuthenticationInfo認(rèn)證通過(guò)填充到SimpleAuthenticationInfo)
ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();
//根據(jù)身份信息獲取權(quán)限信息,
//模擬從數(shù)據(jù)庫(kù)中獲取到的動(dòng)態(tài)權(quán)限數(shù)據(jù)
List<String> permissions=new ArrayList<>();
permissions.add("user:create");//模擬user的創(chuàng)建權(quán)限
permissions.add("item:query");//模擬查詢(xún)權(quán)限
permissions.add("item:add");//模擬商品的添加權(quán)限
permissions.add("item:edit");//模擬修改權(quán)限
//查到權(quán)限數(shù)據(jù),返回授權(quán)信息(包括上邊的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
//將上邊查詢(xún)到授權(quán)信息填充到simpleAuthorizationInfo對(duì)象中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
在該自定義Realm中設(shè)置從數(shù)據(jù)庫(kù)中查詢(xún)到的用戶(hù)權(quán)限有創(chuàng)建用戶(hù)、查詢(xún)商品、添加商品、編輯商品等權(quán)限,所以我們?cè)谶\(yùn)行程序后訪問(wèn)服務(wù)器后就會(huì)得到這些權(quán)限。
9.問(wèn)題總結(jié)
1、在applicationContext-shiro.xml中配置過(guò)慮器鏈接,需要將全部的url和權(quán)限對(duì)應(yīng)起來(lái)進(jìn)行配置,比較發(fā)麻不方便使用。
2、每次授權(quán)都需要調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù),對(duì)于系統(tǒng)性能有很大影響,可以通過(guò)shiro緩存來(lái)解決。
10.Shiro的過(guò)濾器
| 過(guò)濾器簡(jiǎn)稱(chēng) | 對(duì)應(yīng)的java類(lèi) |
|---|---|
| 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,anon后面沒(méi)有參數(shù),表示該路徑下的資源可以匿名使用。
authc:例如/admins/user/**=authc,authc后面沒(méi)有參數(shù),表示該路徑下的資源需要認(rèn)證(登錄)才能使用,F(xiàn)ormAuthenticationFilter是表單認(rèn)證,沒(méi)有參數(shù)。
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()方法。
user:例如/admins/user/**=user,user后面沒(méi)有參數(shù),表示必須存在用戶(hù), 身份認(rèn)證通過(guò)或通過(guò)記住我認(rèn)證通過(guò)的可以訪問(wèn),當(dāng)?shù)侨氩僮鲿r(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()方法。
rest:例如/admins/user/**=rest[user],根據(jù)請(qǐng)求的方法,相當(dāng)于/admins/user/**=perms[user:method],其中method為post,get,delete等。
上述涉及到的過(guò)濾器中:anon,authcBasic,auchc,user是認(rèn)證過(guò)濾器,perms,roles,ssl,rest,port是授權(quán)過(guò)濾器。
上面我們自定義的Realm進(jìn)行認(rèn)證和授權(quán)時(shí)都是通過(guò)將用戶(hù)輸入的信息和我們自己給的數(shù)據(jù)進(jìn)行比對(duì),而沒(méi)有從數(shù)據(jù)庫(kù)中查詢(xún)到相關(guān)信息。所以接下來(lái)要講通過(guò)Realm從數(shù)據(jù)庫(kù)中查詢(xún)認(rèn)證數(shù)據(jù)和權(quán)限數(shù)據(jù)的開(kāi)發(fā)重新實(shí)現(xiàn)上述的登錄和授權(quán)功能。
11.通過(guò)查詢(xún)數(shù)據(jù)庫(kù)完成認(rèn)證
11.1需求
修改realm的doGetAuthenticationInfo()方法,從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息,realm返回的用戶(hù)信息中包括(數(shù)據(jù)庫(kù)庫(kù)中經(jīng)過(guò)md5加密后的串和salt),實(shí)現(xiàn)讓shiro進(jìn)行散列串的校驗(yàn)。
11.2修改doGetAuthenticationInfo從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息
修改自定義CustomRealm代碼,由于要向數(shù)據(jù)庫(kù)中查詢(xún)數(shù)據(jù),所以需要在CustomRealm中注入SysService對(duì)象。修改后的代碼如下:
public class CustomRealm extends AuthorizingRealm
{
//注入service
@Autowired
private SysService sysService;
//設(shè)置realm的名稱(chēng)
@Override
public void setName(String name) {
super.setName("customRealm");
}
//realm的認(rèn)證方法,從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token是用戶(hù)輸入的
//第一步:叢token中取出身份信息
String userCode= (String) token.getPrincipal();
//第二步:根據(jù)用戶(hù)輸入的userCode叢數(shù)據(jù)庫(kù)查詢(xún)
SysUser sysUser =null;
try {
sysUser=sysService.findSysUserByUserCode(userCode);
} catch (Exception e) {
e.printStackTrace();
}
//判斷是否從數(shù)據(jù)庫(kù)中查詢(xún)到用戶(hù)信息
if (sysService==null)
{
return null;
}
//從數(shù)據(jù)庫(kù)查詢(xún)到的密碼
String password=sysUser.getPassword();
//鹽salt
String salt=sysUser.getSalt();
System.out.println(salt);
//activeUser就是用戶(hù)的身份信息
ActiveUser activeUser=new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//根據(jù)用戶(hù)id取出菜單
//通過(guò)service取出菜單
List<SysPermission> menus=null;
try {
menus=sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
e.printStackTrace();
}
//將用戶(hù)菜單設(shè)置到activeUser
activeUser.setMenus(menus);
//如果查不到返回null,
//如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo
///將activeUser設(shè)置到simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo=new
SimpleAuthenticationInfo(activeUser,password, ByteSource.Util.bytes(salt),this.getName());
return simpleAuthenticationInfo;
}
}
就是將之前的靜態(tài)數(shù)據(jù)換成從數(shù)據(jù)庫(kù)中查詢(xún)到的動(dòng)態(tài)數(shù)據(jù)。
11.3設(shè)置憑證匹配器
數(shù)據(jù)庫(kù)中存儲(chǔ)到的md5的散列值,在realm中需要設(shè)置數(shù)據(jù)庫(kù)中的散列值它使用散列算法及散列次數(shù),讓shiro進(jìn)行散列對(duì)比時(shí)和原始數(shù)據(jù)庫(kù)中的散列值使用的算法一致。在applicationContext-shiro.xml中配置如下內(nèi)容:
<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
并將憑證匹配器設(shè)置到我們自定義realm的配置中,在自定義realm的標(biāo)簽中加入如下標(biāo)簽:
<!--自定義realm-->
<bean id="customRealm" class="shiro.CustomRealm">
<!--將憑證匹配器設(shè)置到realm中,realm按照憑證匹配器要求進(jìn)行散列-->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
這樣我們便通過(guò)realm將用戶(hù)輸入的信息和從數(shù)據(jù)庫(kù)中查到的數(shù)據(jù)進(jìn)行對(duì)比從而完成了認(rèn)證。
12.通過(guò)查詢(xún)數(shù)據(jù)庫(kù)完成授權(quán)
12.1需求
修改realm的doGetAuthorizationInfo()方法從數(shù)據(jù)庫(kù)查詢(xún)權(quán)限信息。授權(quán)的方式上面介紹過(guò)三種,正式開(kāi)發(fā)中我們使用注解式授權(quán)方法和jsp標(biāo)簽授權(quán)方法。
12.2修改doGetAuthorizationInfo從數(shù)據(jù)庫(kù)查詢(xún)權(quán)限
修改自定義Realm中的doGetAuthorizationInfo授權(quán)方法,修改后的代碼如下:
//用于授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//從principals獲取主身份信息
//將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類(lèi)型(在上邊的goGetAuthenticationInfo認(rèn)證通過(guò)填充到SimpleAuthenticationInfo)
ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();
//根據(jù)身份信息獲取權(quán)限信息,
//從數(shù)據(jù)庫(kù)中獲取到的動(dòng)態(tài)權(quán)限數(shù)據(jù)
List<SysPermission> permissionList=null;
try {
permissionList=sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
e.printStackTrace();
}
List<String> permissions=new ArrayList<>();
if (permissionList!=null)
{
for (SysPermission sysPermission:permissionList)
{
//將數(shù)據(jù)庫(kù)中的權(quán)限標(biāo)簽符放入集合
permissions.add(sysPermission.getPercode());
}
}
//查到權(quán)限數(shù)據(jù),返回授權(quán)信息(包括上邊的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
//將上邊查詢(xún)到授權(quán)信息填充到simpleAuthorizationInfo對(duì)象中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
我們之前給用戶(hù)授權(quán)是在applicationContext-shiro.xml中的<value>標(biāo)簽中采用:
/items/queryItems.action=perms[item:query]
<!--商品修改需要商品修改權(quán)限-->
/items/editItems.action=perms[item:edit]
的方式給用戶(hù)訪問(wèn)的資源進(jìn)行授權(quán),所以接下來(lái)我們講解注解授權(quán),將上述進(jìn)行授權(quán)的內(nèi)容注釋掉,注解授權(quán)的步驟如下。
12.3開(kāi)啟controller類(lèi)aop支持
對(duì)系統(tǒng)中類(lèi)的方法給用戶(hù)授權(quán),建議在controller層進(jìn)行方法授權(quán),在springmvc.xml中配置:
<!-- 開(kāi)啟aop,對(duì)類(lèi)代理 -->
<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>
12.4在controller方法中添加注解
給商品查詢(xún)方法添加查詢(xún)商品權(quán)限:
@RequestMapping("/queryItems")
@RequiresPermissions("item:query")
public ModelAndView queryItems() throws Exception {
...
}
給商品修改方法添加商品更新權(quán)限:
@RequestMapping(value = "/editItems",method = RequestMethod.GET)
@RequiresPermissions("item:update")//執(zhí)行此方法需要item:update權(quán)限
public String editItems(Model model, Integer id) throws Exception
{
...
}
給商品修改頁(yè)面的提交方法添加商品更新權(quán)限:
@RequestMapping("/editItemSubmit")
@RequiresPermissions("item:update")//執(zhí)行此方法需要item:update權(quán)限
public String editItemSubmit(Model model,Integer id,
@Validated(value = {ValidGroup1.class}) @ModelAttribute(value = "itemsCustom") ItemsCustom itemsCustom,
BindingResult bindingResult,
//上傳圖片
MultipartFile pictureFile
) throws Exception
{
...
}
另一種方式在jsp標(biāo)簽授權(quán)。
12.5jsp標(biāo)簽授權(quán)
在itemsList.jsp頁(yè)面最上方添加如下標(biāo)簽:
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
然后修改itemsList.jsp頁(yè)面部分內(nèi)容:
<td>
<!--有item:update權(quán)限才現(xiàn)實(shí)修改鏈接,沒(méi)有權(quán)限則不顯示修改鏈接-->
<shiro:hasPermission name="item:update">
<a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a>
</shiro:hasPermission>
</td>
相關(guān)jsp標(biāo)簽授權(quán)的解釋如下表:
| 標(biāo)簽名稱(chēng) | 標(biāo)簽條件(均是顯示標(biāo)簽內(nèi)容) |
|---|---|
<shiro:authenticated> |
登錄之后 |
<shiro:notAuthenticated> |
不在登錄狀態(tài)時(shí) |
<shiro:guest> |
用戶(hù)在沒(méi)有RememberMe時(shí) |
<shiro:user> |
用戶(hù)在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> |
顯示用戶(hù)身份名稱(chēng) |
<shiro:principal property="username"/> |
顯示用戶(hù)身份中的屬性值 |
12.6授權(quán)測(cè)試
當(dāng)調(diào)用controller的一個(gè)方法(比如ItemsController的queryItems()方法),由于該方法加了@RequiresPermissions("item:query") ,shiro調(diào)用realm獲取數(shù)據(jù)庫(kù)中的權(quán)限信息,看"item:query"是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問(wèn),如果存在就授權(quán)通過(guò)。
當(dāng)展示一個(gè)jsp頁(yè)面時(shí),頁(yè)面中如果遇到<shiro:hasPermission name="item:update">,shiro調(diào)用realm獲取數(shù)據(jù)庫(kù)中的權(quán)限信息,看item:update是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問(wèn),如果存在就授權(quán)通過(guò)。
問(wèn)題:只要遇到注解或jsp標(biāo)簽的授權(quán),都會(huì)調(diào)用realm方法查詢(xún)數(shù)據(jù)庫(kù),需要使用緩存解決此問(wèn)題。
13.Shiro緩存
需求:針對(duì)上邊授權(quán)頻繁查詢(xún)數(shù)據(jù)庫(kù),需要使用shiro緩存。
13.1緩存流程
shiro中提供了對(duì)認(rèn)證信息和授權(quán)信息的緩存。shiro默認(rèn)是關(guān)閉認(rèn)證信息緩存的,對(duì)于授權(quán)信息的緩存shiro默認(rèn)開(kāi)啟的。主要研究授權(quán)信息緩存,因?yàn)槭跈?quán)的數(shù)據(jù)量大。
當(dāng)用戶(hù)認(rèn)證通過(guò)時(shí),該用戶(hù)第一次授權(quán):調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù)查詢(xún)?cè)撚脩?hù)的授權(quán)信息然后給該用戶(hù)授權(quán)。該用戶(hù)第二次授權(quán)時(shí):不調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù),直接從緩存中取出授權(quán)信息(權(quán)限標(biāo)識(shí)符)然后給該用戶(hù)授權(quán)。
13.2使用ehcache緩存
13.2.1添加jar包
包括ehcache-core.jar和整合包shiro-ehcache.jar。
13.2.2配置cacheManager
在application-shiro.xml中加入ehcache的緩存管理器配置,如下:
<!-- 緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
然后將緩存管理器注入到securityManager安全管理器中,在安全管理器中加入如下內(nèi)容:
<!--securityManage-->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!--注入緩存管理器-->
<property name="cacheManager" ref="cacheManager"/>
</bean>
然后需要進(jìn)行shiro-ecache的配置,跟我們?cè)贛ybatis中整合ehcache的內(nèi)容一樣,在config包下創(chuàng)建一個(gè)shiro-ehcache.xml文件,內(nèi)容如下:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:緩存數(shù)據(jù)持久化的目錄 地址 -->
<diskStore path="/Users/codingboy/develop/ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
13.2.3緩存清空
需求:如果用戶(hù)正常退出,緩存自動(dòng)清空;如果用戶(hù)非正常退出,緩存自動(dòng)清空。如果修改了用戶(hù)的權(quán)限,而用戶(hù)不退出系統(tǒng),修改的權(quán)限無(wú)法立即生效,需要手動(dòng)進(jìn)行編程實(shí)現(xiàn):
在權(quán)限修改后調(diào)用realm的clearCache方法清除緩存,下邊的代碼正常開(kāi)發(fā)時(shí)要放在service中調(diào)用。這里我們只是進(jìn)行一下測(cè)試。
在realm中添加如下方法:
//清除緩存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
然后便可以進(jìn)行測(cè)試類(lèi)的編寫(xiě)了,在controller包下創(chuàng)建一個(gè)ClearShiroCache.java,代碼如下:
@Controller
public class ClearShiroCache
{
@Autowired
private CustomRealm customRealm;
@RequestMapping("/clearShiroCache")
public String clearShiroCache()
{
//清除緩存,將來(lái)開(kāi)發(fā)要在service調(diào)用
customRealm.clearCached();
return "success";
}
}
然后進(jìn)行測(cè)試,在服務(wù)器中輸入http://localhost:8080/Shiro,進(jìn)行登錄,訪問(wèn)系統(tǒng)首頁(yè)。此時(shí)再輸入http://localhost:8080/Shiro/clearShiroCache即可清除該用戶(hù)的權(quán)限。這里我們只進(jìn)行測(cè)試,以后是在service中進(jìn)行。
14.會(huì)話管理器sessionManager
和shiro整合后,使用shiro的sessionManager對(duì)會(huì)話session進(jìn)行管理,此外shiro還提供sessionDao操作會(huì)話數(shù)據(jù)。
配置sessionManager,在application-shiro.xml中加入會(huì)話管理器的配置內(nèi)容:
<!-- 會(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>
然后將該管理器注入到安全管理器中:
<!--securityManage-->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!--注入緩存管理器-->
<property name="cacheManager" ref="cacheManager"/>
<!--注入會(huì)話管理器-->
<property name="sessionManager" ref="sessionManager" />
</bean>
15.實(shí)現(xiàn)驗(yàn)證碼
15.1思路
shiro使用FormAuthenticationFilter進(jìn)行表單認(rèn)證,驗(yàn)證校驗(yàn)的功能應(yīng)該加在FormAuthenticationFilter中,在認(rèn)證之前進(jìn)行驗(yàn)證碼校驗(yàn),而shiro為我們提供的FormAuthenticationFilter中沒(méi)有對(duì)驗(yàn)證碼進(jìn)行認(rèn)證。所以我們需要寫(xiě)FormAuthenticationFilter的子類(lèi),繼承FormAuthenticationFilter,改寫(xiě)它的認(rèn)證方法,在認(rèn)證之前進(jìn)行驗(yàn)證碼校驗(yàn)。
15.2自定義FormAuthenticationFilter
在src包下的shiro包下創(chuàng)建一個(gè)CustomFormAuthenticationFilter.java,內(nèi)容如下:
public class CustomFromAuthenticationFilter extends FormAuthenticationFilter
{
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//在這里進(jìn)行驗(yàn)證碼的校驗(yàn)
HttpServletRequest httpServletRequest= (HttpServletRequest) request;
HttpSession session=httpServletRequest.getSession();
//取出session中的正確驗(yàn)證碼
String validateCode= (String) session.getAttribute("validateCode");
//取出頁(yè)面的驗(yàn)證碼
String randomcode=httpServletRequest.getParameter("randomcode");
if (randomcode!=null&&validateCode!=null&&!randomcode.equals(validateCode))
{
//如果校驗(yàn)失敗,將驗(yàn)證碼錯(cuò)誤的失敗信息,通過(guò)shiroLoginFailure設(shè)置到request中
httpServletRequest.setAttribute("shiroLoginFailure","randomCodeError");
//拒絕訪問(wèn),不再校驗(yàn)賬號(hào)和密碼
return true;
}
return super.onAccessDenied(request, response);
}
}
15.3配置自定義FormAuthenticationFilter
在shiro中加入配置信息:
<!--自定義form認(rèn)證過(guò)濾器-->
<bean id="formAuthenticationFilter"
class="shiro.CustomFromAuthenticationFilter">
<!-- 表單中賬號(hào)的input名稱(chēng) -->
<property name="usernameParam" value="username" />
<!-- 表單中密碼的input名稱(chēng) -->
<property name="passwordParam" value="password" />
<!--記住我input的名稱(chēng)-->
<property name="rememberMeParam" value="rememberMe"/>
</bean>
然后將它注入到Shiro的過(guò)濾器中,在<bean id="shiroFilter">中加入自定義filter的配置:
<!--自定義filter-->
<property name="filters">
<map>
!-- 將自定義的FormAuthenticationFilter注入shiroFiler中 -->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
然后在login.action中對(duì)驗(yàn)證錯(cuò)誤進(jìn)行解析:
else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("驗(yàn)證碼錯(cuò)誤");
}
在登錄頁(yè)面中添加驗(yàn)證碼:
<TR>
<TD>密 碼:</TD>
<TD><input type="password" id="pwd" name="password" style="WIDTH: 130px" />
</TD>
</TR>
<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>
在shiro的過(guò)濾器filter中配置匿名訪問(wèn)驗(yàn)證碼的圖片資源:
<value>
<!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--驗(yàn)證碼-->
/validatecode.jsp=anon
<!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
/logout.action=logout
<!--商品查詢(xún)需要商品查詢(xún)權(quán)限,取消url攔截配置,采用注解授權(quán)-->
<!--/items/queryItems.action=perms[item:query]-->
<!--<!–商品修改需要商品修改權(quán)限–>-->
<!--/items/editItems.action=perms[item:edit]-->
<!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
</value>
16.實(shí)現(xiàn)"記住我"功能
用戶(hù)登陸選擇“自動(dòng)登陸”本次登陸成功會(huì)向cookie寫(xiě)身份信息,下次登陸從cookie中取出身份信息實(shí)現(xiàn)自動(dòng)登陸。
這里涉及到session的序列化與反序列化,所以涉及到的pojo類(lèi)都應(yīng)該實(shí)現(xiàn)java.io.Serializable接口。首先讓ActiveUser.java實(shí)現(xiàn)java.io.Serializable接口,然后讓SysPermission.java實(shí)現(xiàn)java.io.Serializable接口。
16.1配置rememberMeManager
在application-shiro.xml中加入記住我的管理器,內(nèi)容如下:
<!-- 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">
<!--rememberMe時(shí)cookie的名字-->
<constructor-arg value="rememberMe" />
<!-- 記住我cookie生效時(shí)間30天 -->
<property name="maxAge" value="2592000" />
</bean>
并注入到securityManager中:
<!--securityManage-->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm" />
<!--注入緩存管理器-->
<property name="cacheManager" ref="cacheManager"/>
<!--注入會(huì)話管理器-->
<property name="sessionManager" ref="sessionManager" />
<!-- 記住我 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
然后修改登錄頁(yè)面,加入記住我的按鈕,然后在application-shiro.xml的我們自定義form認(rèn)證過(guò)濾器的配置中加入rememberMe的input名稱(chēng)配置:
<!--自定義form認(rèn)證過(guò)濾器-->
<bean id="formAuthenticationFilter"
class="shiro.CustomFromAuthenticationFilter">
<!-- 表單中賬號(hào)的input名稱(chēng) -->
<property name="usernameParam" value="username" />
<!-- 表單中密碼的input名稱(chēng) -->
<property name="passwordParam" value="password" />
<!--記住我input的名稱(chēng)-->
<property name="rememberMeParam" value="rememberMe"/>
</bean>
然后便可以進(jìn)行測(cè)試,在登錄頁(yè)面輸入登錄信息后點(diǎn)擊下次自動(dòng)登錄,登錄成功后我們查看瀏覽器的cookie緩存會(huì)發(fā)現(xiàn)多了一個(gè)名叫rememberMe的cookie信息。然而此時(shí)我們?nèi)敉顺龅卿?,退回到登錄?yè)面,按理說(shuō)此時(shí)瀏覽器已經(jīng)存在該cookie了所以此時(shí)若我們直接訪問(wèn)系統(tǒng)主頁(yè)是可以直接訪問(wèn)的,然而測(cè)試結(jié)果仍然不行,因?yàn)樵撜?qǐng)求被/**=authc攔截了,所以我們要使用UserFilter,將記住我即可訪問(wèn)的地址配置讓UserFilter攔截。
16.2使用UserFilter
在application-shiro.xml的<value>中加入U(xiǎn)serFilter攔截的資源配置:
<value>
<!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
/images/**=anon
/js/**=anon
/style/**=anon
<!--驗(yàn)證碼-->
/validatecode.jsp=anon
<!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
/logout.action=logout
<!--商品查詢(xún)需要商品查詢(xún)權(quán)限,取消url攔截配置,采用注解授權(quán)-->
<!--/items/queryItems.action=perms[item:query]-->
<!--<!–商品修改需要商品修改權(quán)限–>-->
<!--/items/editItems.action=perms[item:edit]-->
<!--配置記住我或認(rèn)證通過(guò)可以訪問(wèn)的資源url-->
/index.jsp=user
/first.action=user
/welcome.jsp=user
<!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
/** = authc
<!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
</value>
此時(shí)登錄時(shí)點(diǎn)擊記住我,成功登錄后關(guān)掉瀏覽器,再次輸入系統(tǒng)的主頁(yè)地址即可直接訪問(wèn)系統(tǒng)的這三個(gè)資源:/index.jsp、/first.action、welcome.jsp。到此,我們便簡(jiǎn)單的入門(mén)了整合了Shiro框架的SSM的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā)。
2018.3.19更
歡迎加入我的Java交流1群:659957958。群里目前已有1800人,每天都非?;钴S,但為了篩選掉那些不懷好意的朋友進(jìn)來(lái)搞破壞,所以目前入群方式已改成了付費(fèi)方式,你只需要支付9塊錢(qián),即可獲取到群文件中的所有干貨以及群里面各位前輩們的疑惑解答;為了鼓勵(lì)良好風(fēng)氣的發(fā)展,讓每個(gè)新人提出的問(wèn)題都得到解決,所以我將得到的入群收費(fèi)收入都以紅包的形式發(fā)放到那些主動(dòng)給新手們解決疑惑的朋友手中。在這里,我們除了談技術(shù),還談生活、談理想;在這里,我們?yōu)槟愕膶W(xué)習(xí)方向指明方向,為你以后的求職道路提供指路明燈;在這里,我們把所有好用的干貨都與你分享。還在等什么,快加入我們吧!
2018.4.21更:如果群1已滿(mǎn)或者無(wú)法加入,請(qǐng)加Java學(xué)習(xí)交流2群:305335626 。群2作為群1的附屬群,除了日常的技術(shù)交流、資料分享、學(xué)習(xí)方向指明外,還會(huì)在每年互聯(lián)網(wǎng)的秋春招時(shí)節(jié)在群內(nèi)發(fā)布大量的互聯(lián)網(wǎng)內(nèi)推方式,話不多說(shuō),快上車(chē)吧!
17.聯(lián)系
If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.