Shiro常见问题与优化实战指南

性能优化

缓存策略选择

Shiro的权限验证过程可能频繁访问数据库,合理的缓存策略能显著提升性能。

权限缓存时间设置

// 在自定义Realm中配置缓存
public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取授权信息后会自动缓存
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole("admin");
        info.addStringPermission("user:delete");
        return info;
    }
    
    // 配置缓存名称和过期时间
    @PostConstruct
    public void initCredentialsMatcher() {
        setAuthorizationCacheName("authorizationCache");
        setAuthorizationCachingEnabled(true);
    }
}

实践建议

  1. 生产环境建议缓存时间设置为30分钟-2小时
  2. 敏感权限建议缩短缓存时间或实时验证
  3. 使用Redis等分布式缓存时注意序列化配置

减少Realm重复查询

// 实现CacheableAuthenticationInfo接口
public class MyRealm extends AuthorizingRealm 
    implements CacheableAuthenticationInfo {
    
    @Override
    public boolean isAuthenticationCachingEnabled(Object principal, 
        AuthenticationToken token) {
        // 只缓存成功的认证
        return token instanceof UsernamePasswordToken;
    }
    
    @Override
    public Object getAuthenticationCacheKey(PrincipalCollection principals) {
        // 使用用户名作为缓存键
        return principals.getPrimaryPrincipal();
    }
}

安全加固

防止暴力破解

登录失败次数限制实现

public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
    private Cache<String, AtomicInteger> passwordRetryCache;
    
    public RetryLimitCredentialsMatcher(CacheManager cacheManager) {
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }
    
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, 
        AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        if (retryCount.incrementAndGet() > 5) {
            throw new ExcessiveAttemptsException();
        }
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            passwordRetryCache.remove(username);
        }
        return matches;
    }
}

敏感操作二次验证

// 在控制器中添加验证
@RequiresPermissions("account:transfer")
public void transferFunds(BigDecimal amount, String password) {
    Subject subject = SecurityUtils.getSubject();
    if (!subject.isAuthenticated()) {
        throw new UnauthorizedException();
    }
    
    // 二次密码验证
    UsernamePasswordToken token = new UsernamePasswordToken(
        subject.getPrincipal().toString(), 
        password
    );
    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        throw new VerificationFailedException("二次验证失败");
    }
    
    // 执行转账逻辑
    accountService.transfer(amount);
}

调试技巧

Shiro日志配置

logback.xml中添加:

<logger name="org.apache.shiro" level="DEBUG"/>
<logger name="org.apache.shiro.web.filter" level="TRACE"/>

典型调试场景输出:

DEBUG org.apache.shiro.authc.AbstractAuthenticator - Authentication successful
TRACE org.apache.shiro.web.filter.PathMatchingFilter - Matching path [/admin] with pattern [/admin/**]

获取用户上下文

// 在任何地方获取当前用户
Subject currentUser = SecurityUtils.getSubject();

// 检查角色
if (currentUser.hasRole("admin")) {
    // 管理员操作
}

// 检查权限
if (currentUser.isPermitted("user:create")) {
    // 创建用户
}

// 获取会话
Session session = currentUser.getSession();
session.setAttribute("key", "value");

实践建议

  1. 避免频繁调用SecurityUtils.getSubject(),可缓存结果
  2. Web环境中注意线程安全问题
  3. 异步任务中需要手动绑定Subject

总结优化方案

图1

通过以上优化措施,可以使Shiro在保证安全性的同时获得更好的性能表现。建议在实际项目中根据具体场景选择合适的优化组合。

评论已关闭