一、Shiro 概述與核心架構
1.1 什么是 Shiro?
Apache Shiro 是一個強大且易用的 Java 安全框架,它提供了認證(Authentication)、授權(Authorization)、加密(Cryptography)和會話管理(Session Management)等功能。與 Spring Security 相比,Shiro 的設計更加直觀和簡單,同時又不失靈活性。
Shiro 的核心優勢:
- 簡單性:API 設計友好,學習曲線平緩
- 全面性:覆蓋了應用安全的各個方面
- 靈活性:可以輕松集成到任何應用環境中
- 可擴展性:所有組件都支持自定義擴展
- 跨平臺:不僅限于 Web 應用,也可用于非 Web 環境
1.2 Shiro 核心架構
Shiro 的架構遵循了"分而治之"的設計理念,將安全功能劃分為多個獨立的組件:
+---------------------------------------------------+
| Application |
+---------------------------------------------------+
| Shiro |
+-------------------+----------------+--------------+
| Authentication | Authorization | Session Mgmt |
+-------------------+----------------+--------------+
| Cryptography | Cache Mgmt | Concurrency |
+-------------------+----------------+--------------+
| Realms |
+---------------------------------------------------+
| Security Manager |
+---------------------------------------------------+
核心組件解析:
- Subject:當前操作用戶的安全特定"視圖"
- SecurityManager:Shiro 的核心,協調各安全組件
- Authenticator:負責認證(登錄)操作
- Authorizer:負責授權(訪問控制)決策
- SessionManager:管理用戶會話
- CacheManager:提供緩存支持以提高性能
- Cryptography:提供加密/解密功能
- Realms:連接安全數據和 Shiro 的橋梁
二、快速入門:第一個 Shiro 應用
2.1 環境準備
2.1.1 添加 Maven 依賴
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.9.0</version>
</dependency>
<!-- 如果需要Web支持 -->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.9.0</version>
</dependency>
<!-- 如果需要與Spring集成 -->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.9.0</version>
</dependency>
2.1.2 基本配置
創建 shiro.ini
配置文件:
[users]
# 格式:username = password, role1, role2, ...
admin = secret, admin
user1 = password, user
user2 = 123456, user[roles]
# 角色權限定義
admin = *
user = user:read,user:write
2.2 第一個 Shiro 示例
public class Quickstart {public static void main(String[] args) {// 1. 創建SecurityManager工廠Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");// 2. 獲取SecurityManager實例SecurityManager securityManager = factory.getInstance();// 3. 綁定SecurityManager到運行環境SecurityUtils.setSecurityManager(securityManager);// 4. 獲取當前用戶(Subject)Subject currentUser = SecurityUtils.getSubject();// 5. 模擬登錄if (!currentUser.isAuthenticated()) {UsernamePasswordToken token = new UsernamePasswordToken("admin", "secret");try {currentUser.login(token);System.out.println("登錄成功!");} catch (AuthenticationException ae) {System.out.println("登錄失敗: " + ae.getMessage());}}// 6. 權限檢查if (currentUser.hasRole("admin")) {System.out.println("您有admin角色");} else {System.out.println("您沒有admin角色");}// 7. 權限檢查if (currentUser.isPermitted("user:create")) {System.out.println("您有創建用戶的權限");} else {System.out.println("您沒有創建用戶的權限");}// 8. 登出currentUser.logout();}
}
三、Shiro 核心概念深入
3.1 Subject 詳解
Subject 是 Shiro 的核心概念,代表當前與應用交互的實體(用戶、第三方服務等)。
關鍵方法:
方法 | 描述 |
---|---|
getPrincipal() | 獲取用戶身份(如用戶名) |
getSession() | 獲取關聯的Session |
login() /logout() | 登錄/登出 |
isAuthenticated() | 是否已認證 |
hasRole() | 檢查角色 |
isPermitted() | 檢查權限 |
多線程環境中的Subject:
// 在另一個線程中獲取Subject
Runnable runnable = () -> {Subject subject = SecurityUtils.getSubject();// 執行操作
};
new Thread(runnable).start();
3.2 SecurityManager 解析
SecurityManager 是 Shiro 架構的核心,負責協調各安全組件。
常見實現:
DefaultSecurityManager
:默認實現DefaultWebSecurityManager
:Web應用專用
配置示例:
// 創建Realm
Realm realm = new IniRealm("classpath:shiro.ini");// 創建SecurityManager
SecurityManager securityManager = new DefaultSecurityManager(realm);// 配置SecurityManager
SecurityUtils.setSecurityManager(securityManager);
3.3 Realm 深入
Realm 是 Shiro 與應用安全數據的橋梁,負責獲取認證和授權信息。
內置Realm實現:
IniRealm
:從INI文件讀取用戶信息JdbcRealm
:從數據庫讀取用戶信息TextConfigurationRealm
:內存配置LdapRealm
:連接LDAP服務器ActiveDirectoryRealm
:連接Active Directory
自定義Realm示例:
public class MyRealm extends AuthorizingRealm {// 授權@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal();SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 添加角色info.addRoles(getRolesFromDB(username));// 添加權限info.addStringPermissions(getPermissionsFromDB(username));return info;}// 認證@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;String username = upToken.getUsername();// 從數據庫獲取用戶信息User user = getUserFromDB(username);if (user == null) {throw new UnknownAccountException("用戶不存在");}// 返回認證信息return new SimpleAuthenticationInfo(user.getUsername(), // 身份user.getPassword(), // 憑證getName() // realm名稱);}// 模擬從數據庫獲取角色private Set<String> getRolesFromDB(String username) {// 實際應用中應從數據庫查詢Set<String> roles = new HashSet<>();if ("admin".equals(username)) {roles.add("admin");roles.add("user");} else {roles.add("user");}return roles;}// 模擬從數據庫獲取權限private Set<String> getPermissionsFromDB(String username) {Set<String> permissions = new HashSet<>();if ("admin".equals(username)) {permissions.add("user:create");permissions.add("user:update");permissions.add("user:delete");permissions.add("user:view");} else {permissions.add("user:view");}return permissions;}// 模擬從數據庫獲取用戶private User getUserFromDB(String username) {if ("admin".equals(username)) {return new User("admin", "secret");} else if ("user".equals(username)) {return new User("user", "password");}return null;}// 簡單的User類private static class User {private String username;private String password;public User(String username, String password) {this.username = username;this.password = password;}public String getUsername() { return username; }public String getPassword() { return password; }}
}
四、Shiro 認證機制
4.1 認證流程詳解
Shiro 的認證流程可以分為以下幾個步驟:
- 收集用戶身份/憑證:通常是用戶名/密碼
- 提交認證:調用
Subject.login()
方法 - Realm 認證:SecurityManager 委托 Realm 進行認證
- 成功/失敗處理:返回認證結果
認證流程圖:
+---------------+ +----------------+ +-------------------+
| Subject | --> | SecurityManager| --> | Authenticator |
+---------------+ +----------------+ +-------------------+|v+-------------------+| Realm(s) |+-------------------+
4.2 多種認證方式
4.2.1 用戶名/密碼認證
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true); // 記住我功能try {currentUser.login(token);// 認證成功
} catch (UnknownAccountException uae) {// 用戶名不存在
} catch (IncorrectCredentialsException ice) {// 密碼錯誤
} catch (LockedAccountException lae) {// 賬戶鎖定
} catch (AuthenticationException ae) {// 其他認證錯誤
}
4.2.2 多Realm認證
配置多個Realm:
[main]
# 定義多個Realm
realm1 = com.example.MyRealm1
realm2 = com.example.MyRealm2# 配置認證策略
authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy# 配置SecurityManager
securityManager.realms = $realm1, $realm2
securityManager.authenticator.authenticationStrategy = $authcStrategy
認證策略:
FirstSuccessfulStrategy
:第一個成功認證的Realm即返回AtLeastOneSuccessfulStrategy
:至少一個Realm認證成功AllSuccessfulStrategy
:所有Realm都必須認證成功
4.3 記住我功能
Shiro 提供了開箱即用的"記住我"功能:
token.setRememberMe(true);
配置RememberMe:
[main]
# Cookie配置
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cookie.name = rememberMe
rememberMeManager.cookie.maxAge = 2592000 # 30天securityManager.rememberMeManager = $rememberMeManager
五、Shiro 授權機制
5.1 授權基礎
Shiro 支持三種授權方式:
-
編程式:在代碼中檢查權限
if (subject.hasRole("admin")) {// 有admin角色 } if (subject.isPermitted("user:create")) {// 有創建用戶的權限 }
-
注解式:使用Java注解
@RequiresRoles("admin") public void deleteUser() {// 需要admin角色才能執行 }@RequiresPermissions("user:create") public void createUser() {// 需要user:create權限才能執行 }
-
標簽式(JSP/GSP):
<shiro:hasRole name="admin"><!-- 只有admin角色能看到的內容 --> </shiro:hasRole><shiro:hasPermission name="user:create"><!-- 有user:create權限能看到的內容 --> </shiro:hasPermission>
5.2 權限字符串詳解
Shiro 的權限字符串支持通配符和多種格式:
-
簡單格式:
printer:query
- 第一部分:資源類型(如printer)
- 第二部分:操作(如query)
-
多級格式:
printer:query:lp7200
- 第三部分:資源實例ID
-
通配符:
printer:*
:對printer的所有操作printer:query:*
:查詢所有printer實例*:query
:對所有資源的查詢操作
5.3 自定義授權
5.3.1 自定義Permission
public class MyPermission implements Permission {private String resource;private String action;private String instance;public MyPermission(String permissionString) {String[] parts = permissionString.split(":");this.resource = parts.length > 0 ? parts[0] : null;this.action = parts.length > 1 ? parts[1] : null;this.instance = parts.length > 2 ? parts[2] : null;}@Overridepublic boolean implies(Permission p) {if (!(p instanceof MyPermission)) {return false;}MyPermission mp = (MyPermission) p;// 檢查資源是否匹配if (resource != null && !resource.equals(mp.resource)) {return false;}// 檢查操作是否匹配if (action != null && !action.equals(mp.action)) {return false;}// 檢查實例是否匹配if (instance != null && !instance.equals(mp.instance)) {return false;}return true;}
}
5.3.2 自定義RolePermissionResolver
public class MyRolePermissionResolver implements RolePermissionResolver {@Overridepublic Collection<Permission> resolvePermissionsInRole(String roleString) {Set<Permission> permissions = new HashSet<>();if ("admin".equals(roleString)) {permissions.add(new WildcardPermission("user:*"));permissions.add(new WildcardPermission("system:*"));} else if ("user".equals(roleString)) {permissions.add(new WildcardPermission("user:read,update"));}return permissions;}
}
六、Shiro 與 Web 集成
6.1 Shiro Filter 詳解
Shiro 為 Web 應用提供了一系列 Filter:
Filter Name | 描述 |
---|---|
anon | 匿名訪問,不需要認證 |
authc | 需要認證才能訪問 |
authcBasic | 基本HTTP認證 |
logout | 退出登錄 |
noSession | 不創建會話 |
perms | 需要特定權限 |
port | 需要特定端口 |
rest | REST風格權限檢查 |
roles | 需要特定角色 |
ssl | 需要HTTPS |
user | 需要已認證或記住我 |
配置示例(web.xml):
<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>
配置示例(shiro.ini):
[urls]
/login = authc
/logout = logout
/static/** = anon
/admin/** = authc, roles[admin]
/user/** = authc, perms["user:read"]
/** = user
6.2 會話管理
Shiro 提供了強大的會話管理功能,可以獨立于容器:
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("key", "value");
String value = (String) session.getAttribute("key");
會話配置:
[main]
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager# 會話超時時間(毫秒)
sessionManager.globalSessionTimeout = 1800000 # 30分鐘# 會話驗證間隔
sessionManager.sessionValidationInterval = 1800000 # 30分鐘securityManager.sessionManager = $sessionManager
6.3 記住我與SSO
記住我配置:
[main]
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
rememberMeManager.cipherKey = base64:abc123...= # 加密密鑰
securityManager.rememberMeManager = $rememberMeManager
SSO集成(以CAS為例):
[main]
casRealm = org.apache.shiro.cas.CasRealm
casRealm.casServerUrlPrefix = https://cas.example.org/cas
casRealm.applicationUrl = https://myapp.example.orgsecurityManager.realms = $casRealm
七、Shiro 高級主題
7.1 緩存集成
Shiro 支持多種緩存實現(EhCache、Redis等):
[main]
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile = classpath:ehcache.xmlsecurityManager.cacheManager = $cacheManager
7.2 加密與哈希
Shiro 提供了強大的加密工具:
// 使用MD5哈希
String hashed = new Md5Hash("password", "salt", 2).toHex();// 使用SHA-256哈希
String hashed = new Sha256Hash("password", "salt", 1024).toBase64();// 使用BCrypt
String hashed = new BCryptPasswordEncoder().encode("password");
配置密碼服務:
[main]
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
credentialsMatcher.hashIterations = 1024
credentialsMatcher.storedCredentialsHexEncoded = truemyRealm = com.example.MyRealm
myRealm.credentialsMatcher = $credentialsMatchersecurityManager.realms = $myRealm
7.3 并發控制
Shiro 可以控制并發登錄:
[main]
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionManager.sessionDAO = $sessionDAO# 配置并發控制
concurrencyFilter = org.apache.shiro.web.filter.session.ConcurrentSessionFilter
concurrencyFilter.sessionManager = $sessionManager
concurrencyFilter.kickoutUrl = /login?kickout=1securityManager.sessionManager = $sessionManager
八、Shiro 與 Spring/Spring Boot 集成
8.1 與 Spring 集成
配置示例:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"/><property name="loginUrl" value="/login.jsp"/><property name="successUrl" value="/home.jsp"/><property name="unauthorizedUrl" value="/unauthorized.jsp"/><property name="filterChainDefinitions"><value>/static/** = anon/login = authc/logout = logout/** = user</value></property>
</bean><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="myRealm"/>
</bean><bean id="myRealm" class="com.example.MyRealm"><property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean><bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Sha256CredentialsMatcher"><property name="hashIterations" value="1024"/>
</bean><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
8.2 與 Spring Boot 集成
依賴配置:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.9.0</version>
</dependency>
配置類:
@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();factoryBean.setSecurityManager(securityManager);Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();filterChainDefinitionMap.put("/static/**", "anon");filterChainDefinitionMap.put("/login", "authc");filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/**", "user");factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);factoryBean.setLoginUrl("/login");factoryBean.setSuccessUrl("/home");factoryBean.setUnauthorizedUrl("/unauthorized");return factoryBean;}@Beanpublic SecurityManager securityManager(Realm realm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(realm);return securityManager;}@Beanpublic Realm realm() {MyRealm realm = new MyRealm();realm.setCredentialsMatcher(credentialsMatcher());return realm;}@Beanpublic CredentialsMatcher credentialsMatcher() {return new Sha256CredentialsMatcher();}@Beanpublic static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}
}
九、Shiro 最佳實踐
9.1 安全最佳實踐
-
密碼安全:
- 始終使用加鹽哈希
- 使用強哈希算法(如SHA-256、BCrypt)
- 避免使用弱密碼
-
會話安全:
- 使用HTTPS保護會話
- 設置合理的會話超時
- 防止會話固定攻擊
-
權限設計:
- 遵循最小權限原則
- 使用RBAC(基于角色的訪問控制)模型
- 定期審查權限分配
9.2 性能優化
-
緩存策略:
- 緩存認證和授權信息
- 使用分布式緩存(如Redis)用于集群環境
-
會話管理:
- 對于無狀態API,考慮禁用會話
- 優化會話持久化策略
-
Realm優化:
- 批量獲取權限信息
- 避免重復查詢數據庫
9.3 常見問題解決
-
ClassCastException:
- 確保Subject綁定到正確的線程
- 檢查類加載器問題
-
權限不生效:
- 檢查權限字符串格式
- 確認Realm正確配置
- 檢查緩存是否過期
-
會話丟失:
- 檢查集群配置
- 確認會話持久化配置正確
十、總結
Apache Shiro 作為一個功能全面而又簡單易用的安全框架,為Java應用提供了強大的安全保障。通過本文的學習,我們了解了:
- Shiro 的核心概念和架構設計
- 認證和授權的實現原理
- Web集成和會話管理
- 與Spring/Spring Boot的集成
- 高級特性和最佳實踐
無論是簡單的Web應用還是復雜的企業級系統,Shiro都能提供合適的安全解決方案。希望本文能成為你Shiro學習之旅的有力參考,幫助你在實際項目中構建更加安全可靠的系統。
PS:如果你在學習過程中遇到問題,別擔心!歡迎在評論區留言,我會盡力幫你解決!😄