更新時間:2021-11-16 10:34:09 來源:動力節(jié)點(diǎn) 瀏覽1386次
Shiro 使用了與 Servlet 一樣的 Filter 接口進(jìn)行擴(kuò)展
1.NameableFilter
NameableFilter 給 Filter 起個名字,如果沒有設(shè)置默認(rèn)就是 FilterName;還記得之前的如 authc 嗎?當(dāng)我們組裝攔截器鏈時會根據(jù)這個名字找到相應(yīng)的攔截器實(shí)例;
2.OncePerRequestFilter
OncePerRequestFilter 用于防止多次執(zhí)行 Filter 的;也就是說一次請求只會走一次攔截器鏈;另外提供 enabled 屬性,表示是否開啟該攔截器實(shí)例,默認(rèn) enabled=true 表示開啟,如果不想讓某個攔截器工作,可以設(shè)置為 false 即可。
3.ShiroFilter
ShiroFilter 是整個 Shiro 的入口點(diǎn),用于攔截需要安全控制的請求進(jìn)行處理,這個之前已經(jīng)用過了。
4.AdviceFilter
AdviceFilter 提供了 AOP 風(fēng)格的支持,類似于 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 中的前置增強(qiáng);在攔截器鏈執(zhí)行之前執(zhí)行;如果返回 true 則繼續(xù)攔截器鏈;否則中斷后續(xù)的攔截器鏈的執(zhí)行直接返回;進(jìn)行預(yù)處理(如基于表單的身份驗證、授權(quán))
postHandle:類似于 AOP 中的后置返回增強(qiáng);在攔截器鏈執(zhí)行完成后執(zhí)行;進(jìn)行后處理(如記錄執(zhí)行時間之類的);
afterCompletion:類似于 AOP 中的后置最終增強(qiáng);即不管有沒有異常都會執(zhí)行;可以進(jìn)行清理資源(如接觸 Subject 與線程的綁定之類的);
5.PathMatchingFilter
PathMatchingFilter 提供了基于 Ant 風(fēng)格的請求路徑匹配功能及攔截器參數(shù)解析的功能,如“roles[admin,user]”自動根據(jù)“,”分割解析到一個路徑參數(shù)配置并綁定到相應(yīng)的路徑:
boolean pathsMatch(String path, ServletRequest request)
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
pathsMatch:該方法用于 path 與請求路徑進(jìn)行匹配的方法;如果匹配返回 true;
onPreHandle:在 preHandle 中,當(dāng) pathsMatch 匹配一個路徑后,會調(diào)用 opPreHandler 方法并將路徑綁定參數(shù)配置傳給 mappedValue;然后可以在這個方法中進(jìn)行一些驗證(如角色授權(quán)),如果驗證失敗可以返回 false 中斷流程;默認(rèn)返回 true;也就是說子類可以只實(shí)現(xiàn) onPreHandle 即可,無須實(shí)現(xiàn) preHandle。如果沒有 path 與請求路徑匹配,默認(rèn)是通過的(即 preHandle 返回 true)。
6.AccessControlFilter
AccessControlFilter 提供了訪問控制的基礎(chǔ)功能;比如是否允許訪問/當(dāng)訪問拒絕時如何處理等:
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]配置中攔截器參數(shù)部分,如果允許訪問返回 true,否則 false;
onAccessDenied:表示當(dāng)訪問拒絕時是否已經(jīng)處理了;如果返回 true 表示需要繼續(xù)處理;如果返回 false 表示該攔截器實(shí)例已經(jīng)處理了,將直接返回即可。
onPreHandle 會自動調(diào)用這兩個方法決定是否繼續(xù)處理:
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
另外 AccessControlFilter 還提供了如下方法用于處理如登錄成功后/重定向到上一個請求:
void setLoginUrl(String loginUrl) //身份驗證時使用,默認(rèn)/login.jsp
String getLoginUrl()
Subject getSubject(ServletRequest request, ServletResponse response) //獲取Subject 實(shí)例
boolean isLoginRequest(ServletRequest request, ServletResponse response)//當(dāng)前請求是否是登錄請求
void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //將當(dāng)前請求保存起來并重定向到登錄頁面
void saveRequest(ServletRequest request) //將請求保存起來,如登錄成功后再重定向回該請求
void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登錄頁面
比如基于表單的身份驗證就需要使用這些功能。
到此基本的攔截器就完事了,如果我們想進(jìn)行訪問訪問的控制就可以繼承 AccessControlFilter;如果我們要添加一些通用數(shù)據(jù)我們可以直接繼承 PathMatchingFilter。
Shiro 對 Servlet 容器的 FilterChain 進(jìn)行了代理,即 ShiroFilter 在繼續(xù) Servlet 容器的 Filter 鏈的執(zhí)行之前,通過 ProxiedFilterChain 對 Servlet 容器的 FilterChain 進(jìn)行了代理;即先走 Shiro 自己的 Filter 體系,然后才會委托給 Servlet 容器的 FilterChain 進(jìn)行 Servlet 容器級別的 Filter 鏈執(zhí)行;Shiro 的 ProxiedFilterChain 執(zhí)行流程:1、先執(zhí)行 Shiro 自己的 Filter 鏈;2、再執(zhí)行 Servlet 容器的 Filter 鏈(即原始的 Filter)。
而 ProxiedFilterChain 是通過 FilterChainResolver 根據(jù)配置文件中[urls]部分是否與請求的 URL 是否匹配解析得到的。
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
即傳入原始的 chain 得到一個代理的 chain。
Shiro 內(nèi)部提供了一個路徑匹配的 FilterChainResolver 實(shí)現(xiàn):PathMatchingFilterChainResolver,其根據(jù)[urls]中配置的 url 模式(默認(rèn) Ant 風(fēng)格)=攔截器鏈和請求的 url 是否匹配來解析得到配置的攔截器鏈的;而 PathMatchingFilterChainResolver 內(nèi)部通過 FilterChainManager 維護(hù)著攔截器鏈,比如 DefaultFilterChainManager 實(shí)現(xiàn)維護(hù)著 url 模式與攔截器鏈的關(guān)系。因此我們可以通過 FilterChainManager 進(jìn)行動態(tài)動態(tài)增加 url 模式與攔截器鏈的關(guān)系。
DefaultFilterChainManager 會默認(rèn)添加 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;且創(chuàng)建相應(yīng)的 url 模式與其攔截器關(guān)系鏈。如果使用 Spring 后續(xù)章節(jié)會介紹如果注冊自定義攔截器。
如果想自定義 FilterChainResolver,可以通過實(shí)現(xiàn) WebEnvironment 接口完成:
public class MyIniWebEnvironment extends IniWebEnvironment {
@Override
protected FilterChainResolver createFilterChainResolver() {
//在此處擴(kuò)展自己的FilterChainResolver
return super.createFilterChainResolver();
}
}
FilterChain 之間的關(guān)系。如果想動態(tài)實(shí)現(xiàn) url -攔截器的注冊,就可以通過實(shí)現(xiàn)此處的 FilterChainResolver 來完成,比如:
//1、創(chuàng)建 FilterChainResolver
PathMatchingFilterChainResolver filterChainResolver =
new PathMatchingFilterChainResolver();
//2、創(chuàng)建 FilterChainManager
DefaultFilterChainManager filterChainManager = new DefaultFilterChainManager();
//3、注冊 Filter
for(DefaultFilter filter : DefaultFilter.values()) {
filterChainManager.addFilter(
filter.name(), (Filter) ClassUtils.newInstance(filter.getFilterClass()));
}
//4、注冊 URL-Filter 的映射關(guān)系
filterChainManager.addToChain("/login.jsp", "authc");
filterChainManager.addToChain("/unauthorized.jsp", "anon");
filterChainManager.addToChain("/**", "authc");
filterChainManager.addToChain("/**", "roles", "admin");
//5、設(shè)置 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;
此處自己去實(shí)現(xiàn)注冊 filter,及url 模式與 filter 之間的映射關(guān)系??梢酝ㄟ^定制 FilterChainResolver 或 FilterChainManager 來完成諸如動態(tài) URL 匹配的實(shí)現(xiàn)。
然后再 web.xml 中進(jìn)行如下配置 Environment:
<context-param>
<param-name>shiroEnvironmentClass</param-name> <param-value>com.github.zhangkaitao.shiro.chapter8.web.env.MyIniWebEnvironment</param-value>
</context-param>
通過自定義自己的攔截器可以擴(kuò)展一些功能,諸如動態(tài) url -角色/權(quán)限訪問控制的實(shí)現(xiàn)、根據(jù) Subject 身份信息獲取用戶信息綁定到 Request(即設(shè)置通用數(shù)據(jù))、驗證碼驗證、在線用戶信息的保存等等,因為其本質(zhì)就是一個 Filter;所以 Filter 能做的它就能做。
1.擴(kuò)展 OncePerRequestFilter
OncePerRequestFilter 保證一次請求只調(diào)用一次 doFilterInternal,即如內(nèi)部的 forward 不會再多執(zhí)行一次 doFilterInternal:
public class MyOncePerRequestFilter extends OncePerRequestFilter {
@Override
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
2.擴(kuò)展 AdviceFilter
AdviceFilter 提供了 AOP 的功能,其實(shí)現(xiàn)和 SpringMVC 中的 Interceptor 思想一樣:
public class MyAdviceFilter extends AdviceFilter {
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("====預(yù)處理/前置處理");
return true;//返回 false 將中斷后續(xù)攔截器鏈的執(zhí)行
}
@Override
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("====后處理/后置返回處理");
}
@Override
public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
System.out.println("====完成處理/后置最終處理");
}
}
preHandle:進(jìn)行請求的預(yù)處理,然后根據(jù)返回值決定是否繼續(xù)處理(true:繼續(xù)過濾器鏈);可以通過它實(shí)現(xiàn)權(quán)限控制;
postHandle:執(zhí)行完攔截器鏈之后正常返回后執(zhí)行;
afterCompletion:不管最后有沒有異常,afterCompletion 都會執(zhí)行,完成如清理資源功能。
然后在 shiro.ini 中進(jìn)行如下配置:
[filters]
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter
[urls]
/**=myFilter1,myFilter2
該過濾器的具體使用可參考我的 SpringMVC 教程中的處理器攔截器部分。
3.PathMatchingFilter
PathMatchingFilter 繼承了 AdviceFilter,提供了 url 模式過濾的功能,如果需要對指定的請求進(jìn)行處理,可以擴(kuò)展 PathMatchingFilter:
public class MyPathMatchingFilter extends PathMatchingFilter {
@Override
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:會進(jìn)行 url 模式與請求 url 進(jìn)行匹配,如果匹配會調(diào)用 onPreHandle;如果沒有配置 url 模式 / 沒有 url 模式匹配,默認(rèn)直接返回 true;
onPreHandle:如果 url 模式與請求 url 匹配,那么會執(zhí)行 onPreHandle,并把該攔截器配置的參數(shù)傳入。默認(rèn)什么不處理直接返回 true。
然后在 shiro.ini 中進(jìn)行如下配置:
[filters]
myFilter3=com.github.zhangkaitao.shiro.chapter8.web.filter.MyPathMatchingFilter
[urls]
/**= myFilter3[config]
/** 就是注冊給 PathMatchingFilter 的 url 模式,config 就是攔截器的配置參數(shù),多個之間逗號分隔,onPreHandle 使用 mappedValue 接收參數(shù)值。
4.擴(kuò)展 AccessControlFilter
AccessControlFilter 繼承了 PathMatchingFilter,并擴(kuò)展了了兩個方法:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue)
|| onAccessDenied(request, response, mappedValue);
}
isAccessAllowed:即是否允許訪問,返回 true 表示允許;
onAccessDenied:表示訪問拒絕時是否自己處理,如果返回 true 表示自己不處理且繼續(xù)攔截器鏈執(zhí)行,返回 false 表示自己已經(jīng)處理了(比如重定向到另一個頁面)。
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("訪問拒絕也不自己處理,繼續(xù)攔截器鏈的執(zhí)行");
return true;
}
}
然后在 shiro.ini 中進(jìn)行如下配置:
5.基于表單登錄攔截器
之前我們已經(jīng)使用過 Shiro 內(nèi)置的基于表單登錄的攔截器了,此處自己做一個類似的基于表單登錄的攔截器。
public class FormLoginFilter extends PathMatchingFilter {
private String loginUrl = "/login.jsp";
private String successUrl = "/";
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
if(SecurityUtils.getSubject().isAuthenticated()) {
return true;//已經(jīng)登錄過
}
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;//繼續(xù)過濾器鏈
} else {//保存當(dāng)前地址并重定向到登錄界面
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());
return false;
}
return true;
}
private boolean isLoginRequest(HttpServletRequest req) {
return pathsMatch(loginUrl, WebUtils.getPathWithinApplication(req));
}
}
onPreHandle 主要流程:
首先判斷是否已經(jīng)登錄過了,如果已經(jīng)登錄過了繼續(xù)攔截器鏈即可;
如果沒有登錄,看看是否是登錄請求,如果是 get 方法的登錄頁面請求,則繼續(xù)攔截器鏈(到請求頁面),否則如果是 get 方法的其他頁面請求則保存當(dāng)前請求并重定向到登錄頁面;
如果是 post 方法的登錄頁面表單提交請求,則收集用戶名 / 密碼登錄即可,如果失敗了保存錯誤消息到 “shiroLoginFailure” 并返回到登錄頁面;
如果登錄成功了,且之前有保存的請求,則重定向到之前的這個請求,否則到默認(rèn)的成功頁面。
shiro.ini 配置
[filters]
formLogin=com.github.zhangkaitao.shiro.chapter8.web.filter.FormLoginFilter
[urls]
/test.jsp=formLogin
/login.jsp=formLogin
6.任意角色授權(quán)攔截器
Shiro 提供 roles 攔截器,其驗證用戶擁有所有角色,沒有提供驗證用戶擁有任意角色的攔截器。
public class AnyRolesFilter extends AccessControlFilter {
private String unauthorizedUrl = "/unauthorized.jsp";
private String loginUrl = "/login.jsp";
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
String[] roles = (String[])mappedValue;
if(roles == null) {
return true;//如果沒有設(shè)置角色參數(shù),默認(rèn)成功
}
for(String role : roles) {
if(getSubject(request, response).hasRole(role)) {
return true;
}
}
return false;//跳到onAccessDenied處理
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (subject.getPrincipal() == null) {//表示沒有登錄,重定向到登錄頁面
saveRequest(request);
WebUtils.issueRedirect(request, response, loginUrl);
} else {
if (StringUtils.hasText(unauthorizedUrl)) {//如果有未授權(quán)頁面跳轉(zhuǎn)過去
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {//否則返回401未授權(quán)狀態(tài)碼
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
return false;
}
}
流程:
首先判斷用戶有沒有任意角色,如果沒有返回 false,將到 onAccessDenied 進(jìn)行處理;
如果用戶沒有角色,接著判斷用戶有沒有登錄,如果沒有登錄先重定向到登錄;
如果用戶沒有角色且設(shè)置了未授權(quán)頁面(unauthorizedUrl),那么重定向到未授權(quán)頁面;否則直接返回 401 未授權(quán)錯誤碼。
shiro.ini 配置
[filters]
anyRoles=com.github.zhangkaitao.shiro.chapter8.web.filter.AnyRolesFilter
[urls]
/test.jsp=formLogin,anyRoles[admin,user]
/login.jsp=formLogin
此處可以繼承 AuthorizationFilter 實(shí)現(xiàn),其提供了授權(quán)相關(guān)的基礎(chǔ)代碼。如果大家想了解更多相關(guān)知識,可以關(guān)注一下動力節(jié)點(diǎn)的Shiro視頻教程,里面的課程內(nèi)容豐富,適合小白學(xué)習(xí),希望對大家能夠有所幫助。
初級 202925
初級 203221
初級 202629
初級 203743