![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuQTNkV2M2ITN0YzY00CZ0YWYtcjYzMTLwYWZy0SOiJWYwETOi9CX3kDOz8CX0kDMw8CX05WZth2YhRHdh9CXkF2bsBXdvwVbvNmLllXZ0lmLywGZvw1LcpDc0RHaiojIsJye.png)
1、nameablefilter
nameablefilter給filter起個名字,如果沒有設定預設就是filtername;還記得之前的如authc嗎?當我們組裝攔截器鍊時會根據這個名字找到相應的攔截器執行個體;
2、onceperrequestfilter
onceperrequestfilter用于防止多次執行filter的;也就是說一次請求隻會走一次攔截器鍊;另外提供enabled屬性,表示是否開啟該攔截器執行個體,預設enabled=true表示開啟,如果不想讓某個攔截器工作,可以設定為false即可。
3、shirofilter
shirofilter是整個shiro的入口點,用于攔截需要安全控制的請求進行處理,這個之前已經用過了。
4、advicefilter
advicefilter提供了aop風格的支援,類似于springmvc中的interceptor:
boolean prehandle(servletrequest request, servletresponse response) throws exception
void posthandle(servletrequest request, servletresponse response) throws exception
void aftercompletion(servletrequest request, servletresponse response, exception exception) throws exception;
prehandler:類似于aop中的前置增強;在攔截器鍊執行之前執行;如果傳回true則繼續攔截器鍊;否則中斷後續的攔截器鍊的執行直接傳回;進行預處理(如基于表單的身份驗證、授權)
posthandle:類似于aop中的後置傳回增強;在攔截器鍊執行完成後執行;進行後處理(如記錄執行時間之類的);
aftercompletion:類似于aop中的後置最終增強;即不管有沒有異常都會執行;可以進行清理資源(如接觸subject與線程的綁定之類的);
5、pathmatchingfilter
pathmatchingfilter提供了基于ant風格的請求路徑比對功能及攔截器參數解析的功能,如“roles[admin,user]”自動根據“,”分割解析到一個路徑參數配置并綁定到相應的路徑:
boolean pathsmatch(string path, servletrequest request)
boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) throws exception
pathsmatch:該方法用于path與請求路徑進行比對的方法;如果比對傳回true;
onprehandle:在prehandle中,當pathsmatch比對一個路徑後,會調用opprehandler方法并将路徑綁定參數配置傳給mappedvalue;然後可以在這個方法中進行一些驗證(如角色授權),如果驗證失敗可以傳回false中斷流程;預設傳回true;也就是說子類可以隻實作onprehandle即可,無須實作prehandle。如果沒有path與請求路徑比對,預設是通過的(即prehandle傳回true)。
6、accesscontrolfilter
accesscontrolfilter提供了通路控制的基礎功能;比如是否允許通路/當通路拒絕時如何處理等:
abstract boolean isaccessallowed(servletrequest request, servletresponse response, object mappedvalue) throws exception;
boolean onaccessdenied(servletrequest request, servletresponse response, object mappedvalue) throws exception;
abstract boolean onaccessdenied(servletrequest request, servletresponse response) throws exception;
isaccessallowed:表示是否允許通路;mappedvalue就是[urls]配置中攔截器參數部分,如果允許通路傳回true,否則false;
onaccessdenied:表示當通路拒絕時是否已經處理了;如果傳回true表示需要繼續處理;如果傳回false表示該攔截器執行個體已經處理了,将直接傳回即可。
onprehandle會自動調用這兩個方法決定是否繼續處理:
boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) throws exception {
return isaccessallowed(request, response, mappedvalue) || onaccessdenied(request, response, mappedvalue);
}
另外accesscontrolfilter還提供了如下方法用于處理如登入成功後/重定向到上一個請求:
void setloginurl(string loginurl) //身份驗證時使用,預設/login.jsp
string getloginurl()
subject getsubject(servletrequest request, servletresponse response) //擷取subject執行個體
boolean isloginrequest(servletrequest request, servletresponse response)//目前請求是否是登入請求
void saverequestandredirecttologin(servletrequest request, servletresponse response) throws ioexception //将目前請求儲存起來并重定向到登入頁面
void saverequest(servletrequest request) //将請求儲存起來,如登入成功後再重定向回該請求
void redirecttologin(servletrequest request, servletresponse response) //重定向到登入頁面
比如基于表單的身份驗證就需要使用這些功能。
到此基本的攔截器就完事了,如果我們想進行通路通路的控制就可以繼承accesscontrolfilter;如果我們要添加一些通用資料我們可以直接繼承pathmatchingfilter。
shiro對servlet容器的filterchain進行了代理,即shirofilter在繼續servlet容器的filter鍊的執行之前,通過proxiedfilterchain對servlet容器的filterchain進行了代理;即先走shiro自己的filter體系,然後才會委托給servlet容器的filterchain進行servlet容器級别的filter鍊執行;shiro的proxiedfilterchain執行流程:1、先執行shiro自己的filter鍊;2、再執行servlet容器的filter鍊(即原始的filter)。
而proxiedfilterchain是通過filterchainresolver根據配置檔案中[urls]部分是否與請求的url是否比對解析得到的。
filterchain getchain(servletrequest request, servletresponse response, filterchain originalchain);
即傳入原始的chain得到一個代理的chain。
shiro内部提供了一個路徑比對的filterchainresolver實作:pathmatchingfilterchainresolver,其根據[urls]中配置的url模式(預設ant風格)=攔截器鍊和請求的url是否比對來解析得到配置的攔截器鍊的;而pathmatchingfilterchainresolver内部通過filterchainmanager維護着攔截器鍊,比如defaultfilterchainmanager實作維護着url模式與攔截器鍊的關系。是以我們可以通過filterchainmanager進行動态動态增加url模式與攔截器鍊的關系。
defaultfilterchainmanager會預設添加org.apache.shiro.web.filter.mgt.defaultfilter中聲明的攔截器:
public enum defaultfilter {
anon(anonymousfilter.class),
authc(formauthenticationfilter.class),
authcbasic(basichttpauthenticationfilter.class),
logout(logoutfilter.class),
nosessioncreation(nosessioncreationfilter.class),
perms(permissionsauthorizationfilter.class),
port(portfilter.class),
rest(httpmethodpermissionfilter.class),
roles(rolesauthorizationfilter.class),
ssl(sslfilter.class),
user(userfilter.class);
下一節會介紹這些攔截器的作用。
如果要注冊自定義攔截器,inisecuritymanagerfactory/webinisecuritymanagerfactory在啟動時會自動掃描ini配置檔案中的[filters]/[main]部分并注冊這些攔截器到defaultfilterchainmanager;且建立相應的url模式與其攔截器關系鍊。如果使用spring後續章節會介紹如果注冊自定義攔截器。
如果想自定義filterchainresolver,可以通過實作webenvironment接口完成:
public class myiniwebenvironment extends iniwebenvironment {
@override
protected filterchainresolver createfilterchainresolver() {
//在此處擴充自己的filterchainresolver
return super.createfilterchainresolver();
}
filterchain之間的關系。如果想動态實作url-攔截器的注冊,就可以通過實作此處的filterchainresolver來完成,比如:
//1、建立filterchainresolver
pathmatchingfilterchainresolver filterchainresolver =
new pathmatchingfilterchainresolver();
//2、建立filterchainmanager
defaultfilterchainmanager filterchainmanager = new defaultfilterchainmanager();
//3、注冊filter
for(defaultfilter filter : defaultfilter.values()) {
filterchainmanager.addfilter(
filter.name(), (filter) classutils.newinstance(filter.getfilterclass()));
}
//4、注冊url-filter的映射關系
filterchainmanager.addtochain("/login.jsp", "authc");
filterchainmanager.addtochain("/unauthorized.jsp", "anon");
filterchainmanager.addtochain("/**", "authc");
filterchainmanager.addtochain("/**", "roles", "admin");
//5、設定filter的屬性
formauthenticationfilter authcfilter =
(formauthenticationfilter)filterchainmanager.getfilter("authc");
authcfilter.setloginurl("/login.jsp");
rolesauthorizationfilter rolesfilter =
(rolesauthorizationfilter)filterchainmanager.getfilter("roles");
rolesfilter.setunauthorizedurl("/unauthorized.jsp");
filterchainresolver.setfilterchainmanager(filterchainmanager);
return filterchainresolver;
此處自己去實作注冊filter,及url模式與filter之間的映射關系。可以通過定制filterchainresolver或filterchainmanager來完成諸如動态url比對的實作。
然後再web.xml中進行如下配置environment:
<context-param>
<param-name>shiroenvironmentclass</param-name> <param-value>com.github.zhangkaitao.shiro.chapter8.web.env.myiniwebenvironment</param-value>
</context-param>
通過自定義自己的攔截器可以擴充一些功能,諸如動态url-角色/權限通路控制的實作、根據subject身份資訊擷取使用者資訊綁定到request(即設定通用資料)、驗證碼驗證、線上使用者資訊的儲存等等,因為其本質就是一個filter;是以filter能做的它就能做。
對于filter的介紹請參考《servlet規範》中的filter部分:
1、擴充onceperrequestfilter
onceperrequestfilter保證一次請求隻調用一次dofilterinternal,即如内部的forward不會再多執行一次dofilterinternal:
public class myonceperrequestfilter extends onceperrequestfilter {
protected void dofilterinternal(servletrequest request, servletresponse response, filterchain chain) throws servletexception, ioexception {
system.out.println("=========once per request filter");
chain.dofilter(request, response);
然後再shiro.ini配置檔案中:
[main]
myfilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.myonceperrequestfilter
#[filters]
#myfilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.myonceperrequestfilter
[urls]
/**=myfilter1
filter可以在[main]或[filters]部分注冊,然後在[urls]部配置設定置url與filter的映射關系即可。
2、擴充advicefilter
advicefilter提供了aop的功能,其實作和springmvc中的interceptor思想一樣:具體可參考我的springmvc教程中的處理器攔截器部分:
<a target="_blank" href="http://www.iteye.com/blogs/subjects/kaitao-springmvc">http://www.iteye.com/blogs/subjects/kaitao-springmvc</a>
public class myadvicefilter extends advicefilter {
protected boolean prehandle(servletrequest request, servletresponse response) throws exception {
system.out.println("====預處理/前置處理");
return true;//傳回false将中斷後續攔截器鍊的執行
protected void posthandle(servletrequest request, servletresponse response) throws exception {
system.out.println("====後處理/後置傳回處理");
public void aftercompletion(servletrequest request, servletresponse response, exception exception) throws exception {
system.out.println("====完成處理/後置最終處理");
prehandle:進行請求的預處理,然後根據傳回值決定是否繼續處理(true:繼續過濾器鍊);可以通過它實作權限控制;
posthandle:執行完攔截器鍊之後正常傳回後執行;
aftercompletion:不管最後有沒有異常,aftercompletion都會執行,完成如清理資源功能。
然後在shiro.ini中進行如下配置:
[filters]
myfilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.myadvicefilter
/**=myfilter1,myfilter2
該過濾器的具體使用可參考我的springmvc教程中的處理器攔截器部分。
3、pathmatchingfilter
pathmatchingfilter繼承了advicefilter,提供了url模式過濾的功能,如果需要對指定的請求進行處理,可以擴充pathmatchingfilter:
public class mypathmatchingfilter extends pathmatchingfilter {
protected boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) throws exception {
system.out.println("url matches,config is " + arrays.tostring((string[])mappedvalue));
return true;
prehandle:會進行url模式與請求url進行比對,如果比對會調用onprehandle;如果沒有配置url模式/沒有url模式比對,預設直接傳回true;
onprehandle:如果url模式與請求url比對,那麼會執行onprehandle,并把該攔截器配置的參數傳入。預設什麼不處理直接傳回true。
然後在shiro.ini中進行如下配置:
myfilter3=com.github.zhangkaitao.shiro.chapter8.web.filter.mypathmatchingfilter
/**= myfilter3[config]
/**就是注冊給pathmatchingfilter的url模式,config就是攔截器的配置參數,多個之間逗号分隔,onprehandle使用mappedvalue接收參數值。
4、擴充accesscontrolfilter
accesscontrolfilter繼承了pathmatchingfilter,并擴充了了兩個方法:
public boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) throws exception {
return isaccessallowed(request, response, mappedvalue)
|| onaccessdenied(request, response, mappedvalue);
isaccessallowed:即是否允許通路,傳回true表示允許;
onaccessdenied:表示通路拒絕時是否自己處理,如果傳回true表示自己不處理且繼續攔截器鍊執行,傳回false表示自己已經處理了(比如重定向到另一個頁面)。
public class myaccesscontrolfilter extends accesscontrolfilter {
protected boolean isaccessallowed(servletrequest request, servletresponse response, object mappedvalue) throws exception {
system.out.println("access allowed");
return true;
protected boolean onaccessdenied(servletrequest request, servletresponse response) throws exception {
system.out.println("通路拒絕也不自己處理,繼續攔截器鍊的執行");
myfilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.myaccesscontrolfilter
/**=myfilter4
5、基于表單登入攔截器
之前我們已經使用過shiro内置的基于表單登入的攔截器了,此處自己做一個類似的基于表單登入的攔截器。
public class formloginfilter extends pathmatchingfilter {
private string loginurl = "/login.jsp";
private string successurl = "/";
if(securityutils.getsubject().isauthenticated()) {
return true;//已經登入過
}
httpservletrequest req = (httpservletrequest) request;
httpservletresponse resp = (httpservletresponse) response;
if(isloginrequest(req)) {
if("post".equalsignorecase(req.getmethod())) {//form表單送出
boolean loginsuccess = login(req); //登入
if(loginsuccess) {
return redirecttosuccessurl(req, resp);
}
}
return true;//繼續過濾器鍊
} else {//儲存目前位址并重定向到登入界面
saverequestandredirecttologin(req, resp);
return false;
private boolean redirecttosuccessurl(httpservletrequest req, httpservletresponse resp) throws ioexception {
webutils.redirecttosavedrequest(req, resp, successurl);
return false;
private void saverequestandredirecttologin(httpservletrequest req, httpservletresponse resp) throws ioexception {
webutils.saverequest(req);
webutils.issueredirect(req, resp, loginurl);
private boolean login(httpservletrequest req) {
string username = req.getparameter("username");
string password = req.getparameter("password");
try {
securityutils.getsubject().login(new usernamepasswordtoken(username, password));
} catch (exception e) {
req.setattribute("shirologinfailure", e.getclass());
private boolean isloginrequest(httpservletrequest req) {
return pathsmatch(loginurl, webutils.getpathwithinapplication(req));
onprehandle主要流程:
1、首先判斷是否已經登入過了,如果已經登入過了繼續攔截器鍊即可;
2、如果沒有登入,看看是否是登入請求,如果是get方法的登入頁面請求,則繼續攔截器鍊(到請求頁面),否則如果是get方法的其他頁面請求則儲存目前請求并重定向到登入頁面;
3、如果是post方法的登入頁面表單送出請求,則收集使用者名/密碼登入即可,如果失敗了儲存錯誤消息到“shirologinfailure”并傳回到登入頁面;
4、如果登入成功了,且之前有儲存的請求,則重定向到之前的這個請求,否則到預設的成功頁面。
shiro.ini配置
formlogin=com.github.zhangkaitao.shiro.chapter8.web.filter.formloginfilter
/test.jsp=formlogin
/login.jsp=formlogin
啟動伺服器輸入http://localhost:8080/chapter8/test.jsp測試時,會自動跳轉到登入頁面,登入成功後又會跳回到test.jsp頁面。
此處可以通過繼承authenticatingfilter實作,其提供了很多登入相關的基礎代碼。另外可以參考shiro内嵌的formauthenticationfilter的源碼,思路是一樣的。
6、任意角色授權攔截器
shiro提供roles攔截器,其驗證使用者擁有所有角色,沒有提供驗證使用者擁有任意角色的攔截器。
public class anyrolesfilter extends accesscontrolfilter {
private string unauthorizedurl = "/unauthorized.jsp";
string[] roles = (string[])mappedvalue;
if(roles == null) {
return true;//如果沒有設定角色參數,預設成功
for(string role : roles) {
if(getsubject(request, response).hasrole(role)) {
return true;
return false;//跳到onaccessdenied處理
subject subject = getsubject(request, response);
if (subject.getprincipal() == null) {//表示沒有登入,重定向到登入頁面
saverequest(request);
webutils.issueredirect(request, response, loginurl);
} else {
if (stringutils.hastext(unauthorizedurl)) {//如果有未授權頁面跳轉過去
webutils.issueredirect(request, response, unauthorizedurl);
} else {//否則傳回401未授權狀态碼
webutils.tohttp(response).senderror(httpservletresponse.sc_unauthorized);
流程:
1、首先判斷使用者有沒有任意角色,如果沒有傳回false,将到onaccessdenied進行處理;
2、如果使用者沒有角色,接着判斷使用者有沒有登入,如果沒有登入先重定向到登入;
3、如果使用者沒有角色且設定了未授權頁面(unauthorizedurl),那麼重定向到未授權頁面;否則直接傳回401未授權錯誤碼。
anyroles=com.github.zhangkaitao.shiro.chapter8.web.filter.anyrolesfilter
/test.jsp=formlogin,anyroles[admin,user]
此處可以繼承authorizationfilter實作,其提供了授權相關的基礎代碼。另外可以參考shiro内嵌的rolesauthorizationfilter的源碼,隻是實作hasallroles邏輯。
shiro内置了很多預設的攔截器,比如身份驗證、授權等相關的。預設攔截器可以參考org.apache.shiro.web.filter.mgt.defaultfilter中的枚舉攔截器:
預設攔截器名
攔截器類
說明(括号裡的表示預設值)
身份驗證相關的
authc
org.apache.shiro.web.filter.authc
.formauthenticationfilter
基于表單的攔截器;如“/**=authc”,如果沒有登入會跳到相應的登入頁面登入;主要屬性:usernameparam:表單送出的使用者名參數名( username); passwordparam:表單送出的密碼參數名(password); remembermeparam:表單送出的密碼參數名(rememberme); loginurl:登入頁面位址(/login.jsp);successurl:登入成功後的預設重定向位址; failurekeyattribute:登入失敗後錯誤資訊存儲key(shirologinfailure);
authcbasic
.basichttpauthenticationfilter
basic http身份驗證攔截器,主要屬性: applicationname:彈出登入框顯示的資訊(application);
logout
.logoutfilter
退出攔截器,主要屬性:redirecturl:退出成功後重定向的位址(/);示例“/logout=logout”
user
.userfilter
使用者攔截器,使用者已經身份驗證/記住我登入的都可;示例“/**=user”
anon
.anonymousfilter
匿名攔截器,即不需要登入即可通路;一般用于靜态資源過濾;示例“/static/**=anon”
授權相關的
roles
org.apache.shiro.web.filter.authz
.rolesauthorizationfilter
角色授權攔截器,驗證使用者是否擁有所有角色;主要屬性: loginurl:登入頁面位址(/login.jsp);unauthorizedurl:未授權後重定向的位址;示例“/admin/**=roles[admin]”
perms
.permissionsauthorizationfilter
權限授權攔截器,驗證使用者是否擁有所有權限;屬性和roles一樣;示例“/user/**=perms["user:create"]”
port
.portfilter
端口攔截器,主要屬性:port(80):可以通過的端口;示例“/test= port[80]”,如果使用者通路該頁面是非80,将自動将請求端口改為80并重定向到該80端口,其他路徑/參數等都一樣
rest
.httpmethodpermissionfilter
rest風格攔截器,自動根據請求方法建構權限字元串(get=read, post=create,put=update,delete=delete,head=read,trace=read,options=read, mkcol=create)建構權限字元串;示例“/users=rest[user]”,會自動拼出“user:read,user:create,user:update,user:delete”權限字元串進行權限比對(所有都得比對,ispermittedall);
ssl
.sslfilter
ssl攔截器,隻有請求協定是https才能通過;否則自動跳轉會https端口(443);其他和port攔截器一樣;
其他
nosessioncreation
org.apache.shiro.web.filter.session
.nosessioncreationfilter
不建立會話攔截器,調用 subject.getsession(false)不會有什麼問題,但是如果 subject.getsession(true)将抛出 disabledsessionexception異常;
另外還提供了一個org.apache.shiro.web.filter.authz.hostfilter,即主機攔截器,比如其提供了屬性:authorizedips:已授權的ip位址,deniedips:表示拒絕的ip位址;不過目前還沒有完全實作,不可用。
這些預設的攔截器會自動注冊,可以直接在ini配置檔案中通過“攔截器名.屬性”設定其屬性:
perms.unauthorizedurl=/unauthorized
另外如果某個攔截器不想使用了可以直接通過如下配置直接禁用:
perms.enabled=false