首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Shiro 學習筆記(八):攔截器機制深度解析與自定義擴展實戰

     編輯:Java 綜合教學

Shiro 學習筆記(八):攔截器機制深度解析與自定義擴展實戰

一、Shiro 攔截器體系架構

Shiro 基於 Servlet Filter 擴展,核心類繼承鏈:

Filter (Servlet)
  ↓ NameableFilter (命名)
    ↓ OncePerRequestFilter (單次請求保證 + enabled 開關)
      ↓ ShiroFilter (總入口)
      ↓ AdviceFilter (AOP 風格: preHandle/postHandle/afterCompletion)
        ↓ PathMatchingFilter (Ant 路徑匹配 + 參數解析)
          ↓ AccessControlFilter (訪問控制: isAccessAllowed/onAccessDenied)
            ↓ 具體功能過濾器

Shiro攔截器類圖

二、核心基類功能詳解

基類核心能力關鍵方法
OncePerRequestFilter防止重複執行、啟用開關doFilterInternal()
AdviceFilterAOP 風格橫切關注點preHandle()/postHandle()/afterCompletion()
PathMatchingFilterAnt 路徑匹配、參數綁定pathsMatch()/onPreHandle(mappedValue)
AccessControlFilter訪問決策、拒絕處理、登錄重定向isAccessAllowed()/onAccessDenied()/saveRequestAndRedirectToLogin()

三、攔截器鏈解析

ProxiedFilterChain 代理機制

Shiro 代理 Servlet 容器原生 FilterChain:

  1. 優先執行 Shiro 內部 Filter 鏈
  2. 委托原生 FilterChain 執行容器級過濾器

解析流程:FilterChainResolver → PathMatchingFilterChainResolver → DefaultFilterChainManager

默認內置過濾器 (DefaultFilter 枚舉)

名稱類用途
anonAnonymousFilter匿名訪問,無需登錄
authcFormAuthenticationFilter表單登錄認證
authcBasicBasicHttpAuthenticationFilterHTTP Basic 認證
logoutLogoutFilter登出處理
rolesRolesAuthorizationFilter角色授權(需所有角色)
permsPermissionsAuthorizationFilter權限授權
userUserFilterRememberMe 或認證用戶
ssl/port/rest對應過濾器SSL/端口/HTTP 方法控制

四、配置與動態注冊

INI 配置示例

[main]
# 註冊自定義過濾器
myFilter1 = com.example.MyFilter
myFilter1.property = value

[urls]
/login.jsp = authc
/** = myFilter1, authc, roles[admin]

程式化動態構建 FilterChain

DefaultFilterChainManager manager = new DefaultFilterChainManager();
// 1. 註冊默認過濾器
DefaultFilter.values().forEach(f -> 
    manager.addFilter(f.name(), (Filter) ClassUtils.newInstance(f.getFilterClass())));

// 2. 構建 URL-Filter 映射
manager.addToChain("/login.jsp", "authc");
manager.addToChain("/**", "authc");
manager.addToChain("/**", "roles", "admin");

// 3. 配置過濾器屬性
FormAuthenticationFilter authc = (FormAuthenticationFilter) manager.getFilter("authc");
authc.setLoginUrl("/login.jsp");
RolesAuthorizationFilter roles = (RolesAuthorizationFilter) manager.getFilter("roles");
roles.setUnauthorizedUrl("/unauthorized.jsp");

// 4. 設置到解析器
PathMatchingFilterChainResolver resolver = new PathMatchingFilterChainResolver();
resolver.setFilterChainManager(manager);

五、自定義過濾器實戰四部曲

1. 擴展 OncePerRequestFilter - 單次請求處理

public class MyOncePerRequestFilter extends OncePerRequestFilter {
    @Override protected void doFilterInternal(ServletRequest req, ServletResponse resp, FilterChain chain) 
            throws ServletException, IOException {
        System.out.println("=== Once per request filter ===");
        chain.doFilter(req, resp);
    }
}

2. 擴展 AdviceFilter - AOP 風格橫切邏輯

public class MyAdviceFilter extends AdviceFilter {
    @Override protected boolean preHandle(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("=== 預處理 ==="); return true; // false 中斷鏈
    }
    @Override protected void postHandle(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("=== 後處理 ===");
    }
    @Override public void afterCompletion(ServletRequest req, ServletResponse resp, Exception ex) throws Exception {
        System.out.println("=== 完成處理/資源清理 ===");
    }
}

3. 擴展 PathMatchingFilter - 路徑匹配參數處理

public class MyPathMatchingFilter extends PathMatchingFilter {
    @Override protected boolean onPreHandle(ServletRequest req, ServletResponse resp, Object mappedValue) 
            throws Exception {
        System.out.println("URL 匹配,配置參數: " + Arrays.toString((String[]) mappedValue));
        return true;
    }
}
// 配置: /admin/** = myFilter3[param1,param2]

4. 擴展 AccessControlFilter - 訪問控制決策

public class MyAccessControlFilter extends AccessControlFilter {
    @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) 
            throws Exception {
        // 業務邏輯判斷是否允許
        return checkPermission(req);
    }
    @Override protected boolean onAccessDenied(ServletRequest req, ServletResponse resp) throws Exception {
        // 拒絕處理:重定向登錄/返回 401/跳轉無權限頁
        saveRequestAndRedirectToLogin(req, resp);
        return false; // 已處理,不繼續鏈
    }
}

六、實戰案例:表單登錄與任意角色授權

自定義表單登錄過濾器

public class FormLoginFilter extends PathMatchingFilter {
    private String loginUrl = "/login.jsp", successUrl = "/";
    
    @Override protected boolean onPreHandle(ServletRequest req, ServletResponse resp, Object mappedValue) 
            throws Exception {
        if (SecurityUtils.getSubject().isAuthenticated()) return true;
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        if (isLoginRequest(request)) {
            if ("post".equalsIgnoreCase(request.getMethod())) {
                boolean success = login(request);
                if (success) return redirectToSuccessUrl(request, response);
            }
            return true; // GET 登錄頁或登錄失敗繼續鏈
        } else {
            saveRequestAndRedirectToLogin(request, response); // 保存目標頁,跳登錄
            return false;
        }
    }
    // login()/redirectToSuccessUrl()/saveRequestAndRedirectToLogin()/isLoginRequest() 實現細節略
}

任意角色授權過濾器 (Shiro 內置 roles 需所有角色)

public class AnyRolesFilter extends AccessControlFilter {
    private String unauthorizedUrl = "/unauthorized.jsp", loginUrl = "/login.jsp";
    
    @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) 
            throws Exception {
        String[] roles = (String[]) mappedValue;
        if (roles == null) return true;
        Subject subject = getSubject(req, resp);
        for (String role : roles) if (subject.hasRole(role)) return true;
        return false; // 觸發 onAccessDenied
    }
    
    @Override protected boolean onAccessDenied(ServletRequest req, ServletResponse resp) throws Exception {
        Subject subject = getSubject(req, resp);
        if (subject.getPrincipal() == null) { // 未登錄
            saveRequest(req);
            WebUtils.issueRedirect(req, resp, loginUrl);
        } else if (StringUtils.hasText(unauthorizedUrl)) { // 已登錄無權限
            WebUtils.issueRedirect(req, resp, unauthorizedUrl);
        } else {
            WebUtils.toHttp(resp).sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
        return false;
    }
}
// 配置: /test.jsp = formLogin, anyRoles[admin,user]

七、自定義 FilterChainResolver 整合 Spring

public class MyIniWebEnvironment extends IniWebEnvironment {
    @Override protected FilterChainResolver createFilterChainResolver() {
        // 完全自定義解析邏輯,如從數據庫加載 URL-權限映射
        return buildCustomResolver();
    }
}
// web.xml 指定環境類

    shiroEnvironmentClass
    com.example.MyIniWebEnvironment

默認過濾器枚舉

八、最佳實踐總結

  1. 選對基類:單次請求→OncePerRequestFilter;AOP→AdviceFilter;路徑匹配→PathMatchingFilter;訪問控制→AccessControlFilter
  2. 最小權限原則:過濾器鏈順序很重要,認證在前,授權在後,業務在後
  3. 異常處理:onAccessDenied 返回 false 表示已處理,true 繼續鏈,避免重複處理
  4. 性能考量:避免在 preHandle 做重 IO,緩存權限元數據
  5. 測試驗證:單元測試覆蓋 isAccessAllowed/onAccessDenied 各分支

掌握 Shiro 攔截器機制,能靈活實現認證授權、日誌審計、限流熔斷等橫切關注點,是構建安全可控 Web 應用的關鍵技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved