shiro的過濾器也是不多的我們可以自定義的方法,它的繼承體系如下: 另外UserFilter是繼承於AccessControlFilter 1、NameableFilter NameableFilter給Filter起個名字,如果沒有設置預設就是FilterName;還記得之前的如authc嗎?當 ...
shiro的過濾器也是不多的我們可以自定義的方法,它的繼承體系如下:
另外UserFilter是繼承於AccessControlFilter
1、NameableFilter
NameableFilter給Filter起個名字,如果沒有設置預設就是FilterName;還記得之前的如authc嗎?當我們組裝過濾器鏈時會根據這個名字找到相應的過濾器實例;
2、OncePerRequestFilter
OncePerRequestFilter用於防止多次執行Filter的;也就是說一次請求只會走一次過濾器鏈;另外提供enabled屬性,表示是否開啟該過濾器實例,預設enabled=true表示開啟,如果不想讓某個過濾器工作,可以設置為false即可。
3、ShiroFilter
ShiroFilter是整個Shiro的入口點,用於過濾需要安全控制的請求進行處理,這個之前已經用過了。
4、AdviceFilter
AdviceFilter提供了AOP風格的支持,類似於SpringMVC中的Interceptor:
Java代碼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]”自動根據“,”分割解析到一個路徑參數配置並綁定到相應的路徑:
Java代碼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提供了訪問控制的基礎功能;比如是否允許訪問/當訪問拒絕時如何處理等:
Java代碼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會自動調用這兩個方法決定是否繼續處理:
Java代碼boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
另外AccessControlFilter還提供瞭如下方法用於處理如登錄成功後/重定向到上一個請求:
Java代碼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) //重定向到登錄頁面
比如基於表單的身份驗證就需要使用這些功能。
(1)如果我們想進行訪問訪問的控制就可以繼承AccessControlFilter
例如: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;//如果沒有設置角色參數,預設成功 } 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)) {//如果有未授權頁面跳轉過去 WebUtils.issueRedirect(request, response, unauthorizedUrl); } else {//否則返回401未授權狀態碼 WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } } return false; } }
流程:
1、首先判斷用戶有沒有任意角色,如果沒有返回false,將到onAccessDenied進行處理;
2、如果用戶沒有角色,接著判斷用戶有沒有登錄,如果沒有登錄先重定向到登錄;
3、如果用戶沒有角色且設置了未授權頁面(unauthorizedUrl),那麼重定向到未授權頁面;否則直接返回401未授權錯誤碼。
(2)FormAuthenticationFilter基於表單登錄攔截器
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;//已經登錄過 } 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()); return false; } return true; } private boolean isLoginRequest(HttpServletRequest req) { return pathsMatch(loginUrl, WebUtils.getPathWithinApplication(req)); } }
onPreHandle主要流程:
1、首先判斷是否已經登錄過了,如果已經登錄過了繼續攔截器鏈即可;
2、如果沒有登錄,看看是否是登錄請求,如果是get方法的登錄頁面請求,則繼續攔截器鏈(到請求頁面),否則如果是get方法的其他頁面請求則保存當前請求並重定向到登錄頁面;
3、如果是post方法的登錄頁面表單提交請求,則收集用戶名/密碼登錄即可,如果失敗了保存錯誤消息到“shiroLoginFailure”並返回到登錄頁面;
4、如果登錄成功了,且之前有保存的請求,則重定向到之前的這個請求,否則到預設的成功頁面。
(3)CasFilter用於客戶端從cas server獲取到ticket後回調應用發起請求的時候被攔截,應用將ticket發送到cas server做驗證
(4)BasicHttpAuthenticationFilter通過Basic認證的方式登錄(例如JWT token方案)