天天看點

shiro 攔截器鍊

shiro 攔截器鍊

1、nameablefilter

nameablefilter給filter起個名字,如果沒有設定預設就是filtername;還記得之前的如authc嗎?當我們組裝攔截器鍊時會根據這個名字找到相應的攔截器執行個體;

2、onceperrequestfilter

onceperrequestfilter用于防止多次執行filter的;也就是說一次請求隻會走一次攔截器鍊;另外提供enabled屬性,表示是否開啟該攔截器執行個體,預設enabled=true表示開啟,如果不想讓某個攔截器工作,可以設定為false即可。

3、shirofilter

shirofilter是整個shiro的入口點,用于攔截需要安全控制的請求進行處理,這個之前已經用過了。

4、advicefilter

advicefilter提供了aop風格的支援,類似于springmvc中的interceptor:

shiro 攔截器鍊

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]”自動根據“,”分割解析到一個路徑參數配置并綁定到相應的路徑:

shiro 攔截器鍊

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提供了通路控制的基礎功能;比如是否允許通路/當通路拒絕時如何處理等:

shiro 攔截器鍊

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會自動調用這兩個方法決定是否繼續處理:

shiro 攔截器鍊

boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) throws exception {  

    return isaccessallowed(request, response, mappedvalue) || onaccessdenied(request, response, mappedvalue);  

}   

另外accesscontrolfilter還提供了如下方法用于處理如登入成功後/重定向到上一個請求: 

shiro 攔截器鍊

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是否比對解析得到的。 

shiro 攔截器鍊

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中聲明的攔截器:

shiro 攔截器鍊

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接口完成:

shiro 攔截器鍊

public class myiniwebenvironment extends iniwebenvironment {  

    @override  

    protected filterchainresolver createfilterchainresolver() {  

        //在此處擴充自己的filterchainresolver  

        return super.createfilterchainresolver();  

    }  

filterchain之間的關系。如果想動态實作url-攔截器的注冊,就可以通過實作此處的filterchainresolver來完成,比如:

shiro 攔截器鍊

//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:  

shiro 攔截器鍊

<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: 

shiro 攔截器鍊

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配置檔案中:

shiro 攔截器鍊

[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>

shiro 攔截器鍊

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中進行如下配置: 

shiro 攔截器鍊

[filters]  

myfilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.myadvicefilter  

/**=myfilter1,myfilter2   

該過濾器的具體使用可參考我的springmvc教程中的處理器攔截器部分。

3、pathmatchingfilter

pathmatchingfilter繼承了advicefilter,提供了url模式過濾的功能,如果需要對指定的請求進行處理,可以擴充pathmatchingfilter: 

shiro 攔截器鍊

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中進行如下配置:

shiro 攔截器鍊

myfilter3=com.github.zhangkaitao.shiro.chapter8.web.filter.mypathmatchingfilter  

/**= myfilter3[config]   

/**就是注冊給pathmatchingfilter的url模式,config就是攔截器的配置參數,多個之間逗号分隔,onprehandle使用mappedvalue接收參數值。

4、擴充accesscontrolfilter

accesscontrolfilter繼承了pathmatchingfilter,并擴充了了兩個方法:

shiro 攔截器鍊

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表示自己已經處理了(比如重定向到另一個頁面)。

shiro 攔截器鍊

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("通路拒絕也不自己處理,繼續攔截器鍊的執行");  

shiro 攔截器鍊

myfilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.myaccesscontrolfilter  

/**=myfilter4  

5、基于表單登入攔截器 

之前我們已經使用過shiro内置的基于表單登入的攔截器了,此處自己做一個類似的基于表單登入的攔截器。

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配置

shiro 攔截器鍊

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攔截器,其驗證使用者擁有所有角色,沒有提供驗證使用者擁有任意角色的攔截器。

shiro 攔截器鍊

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未授權錯誤碼。

shiro 攔截器鍊

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配置檔案中通過“攔截器名.屬性”設定其屬性:

shiro 攔截器鍊

perms.unauthorizedurl=/unauthorized  

另外如果某個攔截器不想使用了可以直接通過如下配置直接禁用:

shiro 攔截器鍊

perms.enabled=false