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

| 基類 | 核心能力 | 關鍵方法 |
|---|---|---|
| OncePerRequestFilter | 防止重複執行、啟用開關 | doFilterInternal() |
| AdviceFilter | AOP 風格橫切關注點 | preHandle()/postHandle()/afterCompletion() |
| PathMatchingFilter | Ant 路徑匹配、參數綁定 | pathsMatch()/onPreHandle(mappedValue) |
| AccessControlFilter | 訪問決策、拒絕處理、登錄重定向 | isAccessAllowed()/onAccessDenied()/saveRequestAndRedirectToLogin() |
Shiro 代理 Servlet 容器原生 FilterChain:
解析流程:FilterChainResolver → PathMatchingFilterChainResolver → DefaultFilterChainManager
| 名稱 | 類 | 用途 |
|---|---|---|
| anon | AnonymousFilter | 匿名訪問,無需登錄 |
| authc | FormAuthenticationFilter | 表單登錄認證 |
| authcBasic | BasicHttpAuthenticationFilter | HTTP Basic 認證 |
| logout | LogoutFilter | 登出處理 |
| roles | RolesAuthorizationFilter | 角色授權(需所有角色) |
| perms | PermissionsAuthorizationFilter | 權限授權 |
| user | UserFilter | RememberMe 或認證用戶 |
| ssl/port/rest | 對應過濾器 | SSL/端口/HTTP 方法控制 |
[main]
# 註冊自定義過濾器
myFilter1 = com.example.MyFilter
myFilter1.property = value
[urls]
/login.jsp = authc
/** = myFilter1, authc, roles[admin]
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);
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);
}
}
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("=== 完成處理/資源清理 ===");
}
}
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]
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() 實現細節略
}
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]
public class MyIniWebEnvironment extends IniWebEnvironment {
@Override protected FilterChainResolver createFilterChainResolver() {
// 完全自定義解析邏輯,如從數據庫加載 URL-權限映射
return buildCustomResolver();
}
}
// web.xml 指定環境類
shiroEnvironmentClass
com.example.MyIniWebEnvironment

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