1. Shiro框架概述
Apache Shiro是一个强大且易用的Java安全框架,它为应用程序提供了认证、授权、加密和会话管理等功能。作为一个轻量级的安全框架,Shiro的设计理念是简化应用程序安全性的实现,同时保持足够的灵活性和扩展性。
Shiro的核心功能可以概括为以下四个方面:
- 认证(Authentication):验证用户身份,通常就是我们常说的"登录"过程
- 授权(Authorization):访问控制,决定用户能做什么
- 会话管理(Session Management):用户特定的时间敏感状态管理
- 密码加密(Cryptography):保护数据安全,防止敏感信息泄露
与其他安全框架相比,Shiro的主要优势在于其简单性和易用性。它不需要依赖任何容器或框架,可以轻松集成到任何Java应用程序中,无论是简单的命令行程序还是复杂的企业级Web应用。
2. Shiro核心组件解析
2.1 Subject:安全操作的主体
Subject是Shiro的核心概念,代表当前与应用程序交互的"用户"。这个用户可以是人,也可以是第三方服务、后台进程等。Subject封装了当前用户的所有安全操作,包括:
// 获取当前Subject Subject currentUser = SecurityUtils.getSubject(); // 登录 currentUser.login(token); // 检查角色 currentUser.hasRole("admin"); // 检查权限 currentUser.isPermitted("user:delete");Subject实际上是一个门面(Facade)模式,它将大部分安全操作委托给SecurityManager处理。
2.2 SecurityManager:安全管理的核心
SecurityManager是Shiro架构的心脏,负责协调所有安全组件的工作。它管理着所有Subject的安全操作,并提供以下服务:
- 认证管理
- 授权管理
- 会话管理
- 缓存管理
在应用程序中,通常只需要配置一个SecurityManager实例。配置示例如下:
[main] # 配置Realm myRealm = com.example.MyRealm securityManager.realms = $myRealm # 配置缓存管理器 cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager securityManager.cacheManager = $cacheManager2.3 Realm:安全数据桥梁
Realm充当Shiro与应用程序安全数据源之间的桥梁。当需要认证用户或检查权限时,Shiro会从配置的Realm中查询相关数据。Shiro支持多种Realm实现:
- IniRealm:从INI配置文件中读取用户和角色信息
- JdbcRealm:通过JDBC从数据库中获取安全数据
- LdapRealm:连接LDAP目录服务
- ActiveDirectoryRealm:集成Active Directory
开发者也可以自定义Realm实现:
public class MyCustomRealm extends AuthorizingRealm { // 认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { // 实现认证逻辑 } // 授权逻辑 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 实现授权逻辑 } }3. 认证流程深度解析
3.1 认证基本流程
Shiro的认证流程可以概括为以下步骤:
- 用户提交身份凭证(通常是用户名/密码)
- 创建AuthenticationToken对象封装凭证
- 调用Subject.login()方法
- SecurityManager处理认证请求
- Realm验证凭证并返回认证信息
- 认证成功或失败处理
代码示例:
// 1. 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 2. 创建认证令牌 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { // 3. 登录认证 currentUser.login(token); // 认证成功处理 } catch (AuthenticationException ae) { // 认证失败处理 }3.2 认证流程源码分析
让我们深入Shiro源码,看看认证过程是如何实现的:
Subject.login()调用:
// DelegatingSubject.java public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); // 处理认证结果... }SecurityManager处理:
// DefaultSecurityManager.java public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info = authenticate(token); Subject loggedIn = createSubject(token, info, subject); onSuccessfulLogin(token, info, loggedIn); return loggedIn; }Authenticator认证:
// ModularRealmAuthenticator.java protected AuthenticationInfo doAuthenticate(AuthenticationToken token) { Collection<Realm> realms = getRealms(); if (realms.size() == 1) { return doSingleRealmAuthentication(realms.iterator().next(), token); } else { return doMultiRealmAuthentication(realms, token); } }Realm验证:
// AuthenticatingRealm.java public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) { // 1. 尝试从缓存获取 AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { // 2. 缓存中没有则调用doGetAuthenticationInfo info = doGetAuthenticationInfo(token); // 3. 缓存认证信息 cacheAuthenticationInfoIfPossible(token, info); } // 4. 验证凭证 if (info != null) { assertCredentialsMatch(token, info); } return info; }
3.3 密码匹配与加密
Shiro提供了强大的密码匹配和加密功能:
密码匹配器:
// 配置密码匹配器 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("SHA-256"); matcher.setHashIterations(1024); myRealm.setCredentialsMatcher(matcher);密码加密:
// 生成加密密码 String hashedPassword = new Sha256Hash(password, salt, 1024).toBase64();盐值使用:
// 在Realm中设置盐值 info.setCredentialsSalt(ByteSource.Util.bytes(salt));
4. 授权机制详解
4.1 授权基本概念
Shiro的授权模型基于三个核心概念:
- 权限(Permission):定义具体的操作权限,如"user:create"
- 角色(Role):权限的集合,如"admin"角色可能包含所有用户管理权限
- 访问控制:检查用户是否具有执行某个操作的权限
Shiro支持多种授权方式:
编程式授权:
if (subject.hasRole("admin")) { // 执行管理操作 } if (subject.isPermitted("user:delete")) { // 删除用户 }注解式授权:
@RequiresRoles("admin") public void deleteUser(User user) { // 只有admin角色可以调用此方法 }标签式授权(JSP/GSP):
<shiro:hasRole name="admin"> <a href="/admin">Admin Panel</a> </shiro:hasRole>
4.2 授权流程源码分析
Shiro的授权流程同样经过多个组件:
授权检查入口:
// AuthorizingSecurityManager.java public void checkPermission(PrincipalCollection principals, String permission) { this.authorizer.checkPermission(principals, permission); }ModularRealmAuthorizer处理:
// ModularRealmAuthorizer.java public void checkPermission(PrincipalCollection principals, String permission) { assertRealmsConfigured(); for (Realm realm : getRealms()) { if (realm instanceof Authorizer) { ((Authorizer) realm).checkPermission(principals, permission); } } }Realm授权实现:
// AuthorizingRealm.java public void checkPermission(PrincipalCollection principals, String permission) { AuthorizationInfo info = getAuthorizationInfo(principals); checkPermission(permission, info); }
4.3 权限字符串设计
Shiro的权限字符串通常采用"资源:操作:实例"的格式:
基本权限:
- "user:create" - 创建用户的权限
- "user:delete" - 删除用户的权限
实例级权限:
- "user:edit:123" - 编辑ID为123的用户的权限
通配符权限:
- "user:*" - 所有用户操作权限
- "*:view" - 查看所有资源的权限
自定义权限解析:
public class MyPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String permissionString) { return new MyWildcardPermission(permissionString); } } // 配置权限解析器 securityManager.setPermissionResolver(new MyPermissionResolver());5. 实际应用与最佳实践
5.1 集成Spring Boot
在Spring Boot中集成Shiro的步骤:
添加依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.8.0</version> </dependency>配置Shiro:
@Configuration public class ShiroConfig { @Bean public Realm realm() { MyRealm realm = new MyRealm(); realm.setCredentialsMatcher(hashedCredentialsMatcher()); return realm; } @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("SHA-256"); matcher.setHashIterations(1024); return matcher; } @Bean public SecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } }
5.2 缓存策略优化
Shiro的缓存机制可以显著提高性能:
启用缓存:
// 配置缓存管理器 CacheManager cacheManager = new EhCacheManager(); securityManager.setCacheManager(cacheManager); // 在Realm中启用缓存 realm.setAuthenticationCachingEnabled(true); realm.setAuthorizationCachingEnabled(true);缓存策略:
- 认证缓存:缓存用户凭证信息
- 授权缓存:缓存用户权限信息
缓存失效:
// 当用户权限变更时清除缓存 public void clearAuthorizationCache(String username) { SimplePrincipalCollection principals = new SimplePrincipalCollection(username, realm.getName()); realm.clearCachedAuthorizationInfo(principals); }
5.3 常见问题与解决方案
认证失败但无异常信息:
- 检查Realm的doGetAuthenticationInfo方法是否返回了正确的AuthenticationInfo
- 确保CredentialsMatcher配置正确
授权缓存不更新:
- 调用realm.clearCachedAuthorizationInfo()清除缓存
- 设置合理的缓存过期时间
RememberMe功能失效:
- 检查Cookie配置是否正确
- 确保RememberMe管理器已正确配置
Session管理问题:
// 配置Session管理器 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(1800000); // 30分钟 securityManager.setSessionManager(sessionManager);性能优化建议:
- 使用缓存减少数据库查询
- 合理设计权限字符串,避免过于复杂的权限检查
- 对于频繁的权限检查,考虑使用@RequiresPermissions注解而非编程式检查
6. 安全增强与扩展
6.1 防止会话固定攻击
Shiro提供了会话固定攻击防护:
@Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionIdUrlRewritingEnabled(false); // 禁用URL重写 sessionManager.setSessionIdCookieEnabled(true); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionIdCookie(new SimpleCookie("SHRIOSESSIONID")); return sessionManager; }6.2 多Realm策略
Shiro支持配置多个Realm并指定认证策略:
@Bean public ModularRealmAuthenticator authenticator() { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); return authenticator; } @Bean public SecurityManager securityManager(Realm realm1, Realm realm2) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setAuthenticator(authenticator()); securityManager.setRealms(Arrays.asList(realm1, realm2)); return securityManager; }6.3 自定义Filter扩展
Shiro的Filter机制允许自定义安全控制:
public class MyCustomFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // 自定义访问控制逻辑 return true; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // 访问被拒绝时的处理 return false; } } // 配置Filter Map<String, Filter> filters = new HashMap<>(); filters.put("custom", new MyCustomFilter()); shiroFilterFactoryBean.setFilters(filters);6.4 分布式会话支持
在分布式环境中使用Shiro:
@Bean public SessionDAO sessionDAO() { RedisSessionDAO sessionDAO = new RedisSessionDAO(); sessionDAO.setRedisManager(redisManager()); return sessionDAO; } @Bean public SessionManager sessionManager(SessionDAO sessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(sessionDAO); return sessionManager; }