在現代企業級應用開發中,權限控制是系統安全的核心防線。Apache Shiro 作為一個強大且輕量級的 Java 安全框架,提供了認證、授權、加密、會話管理等完整的安全解決方案。本文將深入解析 Shiro 的權限認證核心要素、三種授權方式、編程式授權實戰,以及與 Web 應用的整合配置,幫助開發者快速掌握 Shiro 在實際項目中的應用。
權限認證的本質是控制「誰能訪問什麼資源」。在 Shiro 架構中,這圍繞著三個核心概念展開:
user:select、user:delete)。
Shiro 提供了三種授權方式,開發者可根據場景靈活選擇:
在 Java 代碼中通過 API 進行授權判斷,支持基於角色和基於權限兩種模式。適用於複雜業務邏輯中需要動態判斷權限的場景。
使用 @RequiresRoles、@RequiresPermissions 等註解標注在方法或類上,聲明式地實現授權。代碼侵入性小,適合 Spring 等框架整合環境。
通過 <shiro:guest>、<shiro:user>、<shiro:hasRole> 等標籤,在頁面層面直接控制內容顯示。這是 Shiro 靈活性的體現,能大幅減少後台邏輯判斷,前後端分離架構中同樣適用於模板引擎。
[users] java1234=123456,role1,role2 jack=123,role1
上述配置表示:用戶 java1234 擁有 role1 和 role2 角色;用戶 jack 僅擁有 role1 角色。
public class ShiroUtil {
public static Subject login(String configFile, String userName, String password) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
currentUser.login(token);
System.out.println("身份認證成功!");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("身份認證失敗!");
}
return currentUser;
}
}
public class RoleTest {
@Test
public void testHasRole() {
Subject currentUser = ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
System.out.println(currentUser.hasRole("role1") ? "有role1角色" : "無role1角色");
boolean[] results = currentUser.hasRoles(Arrays.asList("role1", "role2", "role3"));
System.out.println(results[0] ? "有role1" : "無role1");
System.out.println(results[1] ? "有role2" : "無role2");
System.out.println(results[2] ? "有role3" : "無role3");
System.out.println(currentUser.hasAllRoles(Arrays.asList("role1", "role2")) ? "同時擁有role1,role2" : "不全擁有");
currentUser.logout();
}
@Test
public void testCheckRole() {
Subject currentUser = ShiroUtil.login("classpath:shiro_role.ini", "java1234", "123456");
currentUser.checkRole("role1"); // 無角色拋出異常
currentUser.checkRoles(Arrays.asList("role1", "role2"));
currentUser.checkRoles("role1", "role2", "role3");
currentUser.logout();
}
}
關鍵區別:hasRole* 返回 boolean,適合條件判斷;checkRole* 無權限時拋出 AuthorizationException,適合強制攔截。
[users] java1234=123456,role1,role2 jack=123,role1 [roles] role1=user:select role2=user:add,user:update,user:delete
public class PermissionTest {
@Test
public void testIsPermitted() {
Subject currentUser = ShiroUtil.login("classpath:shiro_permission.ini", "java1234", "123456");
System.out.println(currentUser.isPermitted("user:select") ? "有查詢權限" : "無查詢權限");
System.out.println(currentUser.isPermitted("user:update") ? "有更新權限" : "無更新權限");
boolean[] results = currentUser.isPermitted("user:select", "user:update", "user:delete");
System.out.println(results[0] ? "有select" : "無select");
System.out.println(results[1] ? "有update" : "無update");
System.out.println(results[2] ? "有delete" : "無delete");
System.out.println(currentUser.isPermittedAll("user:select", "user:update") ? "同時擁有select,update" : "不全擁有");
currentUser.logout();
}
}
核心依賴:shiro-core、shiro-web;日誌:slf4j-api、log4j、commons-logging;Web 支持:javax.servlet-api、jstl。
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
[main] authc.loginUrl=/login roles.unauthorizedUrl=/unauthorized.jsp perms.unauthorizedUrl=/unauthorized.jsp [users] java1234=123456,admin jack=123,teacher marry=234 json=345 [roles] admin=user:* teacher=student:* [urls] /login=anon /admin=authc /student=roles[teacher] /teacher=perms["user:create"]
配置要點:
anon:匿名訪問,無需認證(如登錄頁)authc:需認證後訪問,未登錄自動跳轉至 loginUrlroles[roleName]:需具備指定角色perms["permission"]:需具備指定權限unauthorized.jsppublic class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
String password = req.getParameter("password");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
subject.login(token);
resp.sendRedirect("success.jsp");
} catch (Exception e) {
req.setAttribute("errorInfo", "用戶名或密碼錯誤");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
}
適用於簡單演示、測試環境。用戶、角色、權限直接寫在 shiro.ini 中,修改需重啟應用,不適合生產環境。
核心步驟:
shiro.ini 指定自定義 RealmAuthorizingRealm 重寫兩個核心方法[main] myRealm=com.java.realm.MyRealm securityManager.realms=$myRealm
public class MyRealm extends AuthorizingRealm {
private UserDao userDao = new UserDao();
private DbUtil dbUtil = new DbUtil();
/** 授權:為當前用戶賦予角色與權限 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
try (Connection con = dbUtil.getCon()) {
info.setRoles(userDao.getRoles(con, userName));
info.setStringPermissions(userDao.getPermissions(con, userName));
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
/** 認證:驗證用戶身份 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
try (Connection con = dbUtil.getCon()) {
User user = userDao.getByUserName(con, userName);
if (user != null) {
return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
流程解析:訪問受保護資源 → Shiro Filter 攔截 → 未登錄跳轉登錄頁 → 登錄成功創建 Subject → 訪問控制器 → 執行 subject.login() → 進入 Realm 認證/授權方法 → 查詢數據庫 → 返回封裝信息 → Shiro 完成權限判定。

CredentialsMatcher 使用 MD5/SHA + Salt 加密,不可明文存儲SessionManager 實現分布式會話共享[urls] 配置按順序匹配,通配符路徑放最後UnauthorizedException 處理器,返回友好錯誤頁或 JSONApache Shiro 以「簡單、靈活、強大」著稱,其核心優勢在於:
掌握 Shiro 權限認證的關鍵在於理解「認證與授權分離」、「Realm 作為數據橋樑」、「Filter 鏈路攔截機制」。建議從簡單的 INI 配置入手,逐步遷移到 JDBC Realm,再結合緩存、加密、會話管理構建企業級安全體系。