Shiro常规用法详解:从集成到实战配置

一、集成方式

1. 独立应用集成(非Spring环境)

在纯Java项目中集成Shiro的核心步骤:

// 1. 创建SecurityManager并配置Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(new MyCustomRealm());

// 2. 设置全局SecurityManager
SecurityUtils.setSecurityManager(securityManager);

// 3. 获取当前Subject
Subject currentUser = SecurityUtils.getSubject();

// 4. 执行认证
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
try {
    currentUser.login(token); // 认证成功
} catch (AuthenticationException ae) {
    // 认证失败处理
}

实践建议:适合非Web应用或遗留系统改造,但建议优先考虑Spring集成方案

2. 与Spring/Spring Boot整合

Spring Boot中的典型配置:

@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
    ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
    factoryBean.setSecurityManager(securityManager);
    
    // 配置拦截规则
    Map<String, String> filterChainMap = new LinkedHashMap<>();
    filterChainMap.put("/static/**", "anon");
    filterChainMap.put("/login", "anon");
    filterChainMap.put("/**", "authc");
    factoryBean.setFilterChainDefinitionMap(filterChainMap);
    
    return factoryBean;
}

@Bean
public DefaultWebSecurityManager securityManager(MyRealm realm) {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setRealm(realm);
    manager.setCacheManager(new RedisCacheManager()); // 使用Redis缓存
    return manager;
}

关键点

  • ShiroFilterFactoryBean是Web集成的核心入口
  • 拦截规则按顺序匹配,anon表示匿名访问,authc需要认证
  • 通过DefaultWebSecurityManager管理安全组件

二、配置示例

1. INI配置方式(传统方式)

[main]
# 定义Realm
myRealm = com.example.MyRealm
securityManager.realms = $myRealm

# 加密配置
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName = SHA-256
credentialsMatcher.hashIterations = 1024
myRealm.credentialsMatcher = $credentialsMatcher

[urls]
/login = anon
/admin/** = roles[admin]
/user/** = perms["user:manage"]

适用场景:简单应用或快速原型开发,但灵活性不如Java配置

2. 注解驱动安全控制

@Controller
public class AdminController {
    
    @RequiresRoles("admin")
    @GetMapping("/admin/dashboard")
    public String adminDashboard() {
        return "admin/dashboard";
    }
    
    @RequiresPermissions("user:delete")
    @DeleteMapping("/user/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        // 删除用户逻辑
    }
}

注意:需要在Spring中启用注解支持:

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
    DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
}

三、常见场景实现

1. 登录流程定制

自定义FormAuthenticationFilter示例:

public class CustomFormAuthFilter extends FormAuthenticationFilter {
    
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, 
        Subject subject, ServletRequest request, ServletResponse response) {
        // 登录成功后记录日志或初始化用户会话
        return super.onLoginSuccess(token, subject, request, response);
    }
    
    @Override
    protected boolean onLoginFailure(AuthenticationToken token,
        AuthenticationException e, ServletRequest request, ServletResponse response) {
        // 登录失败处理(如记录失败次数)
        return super.onLoginFailure(token, e, request, response);
    }
}

配置自定义Filter:

@Bean
public ShiroFilterFactoryBean shiroFilter(...) {
    ...
    Map<String, Filter> filters = new HashMap<>();
    filters.put("authc", new CustomFormAuthFilter());
    factoryBean.setFilters(filters);
    ...
}

2. 动态URL权限控制

结合数据库实现动态规则:

public class DynamicFilterChainManager {
    
    public void loadFilterChains(ShiroFilterFactoryBean factoryBean) {
        // 从数据库读取权限规则
        List<PermissionRule> rules = permissionRuleDao.findAll();
        
        Map<String, String> chains = new LinkedHashMap<>();
        rules.forEach(rule -> {
            chains.put(rule.getUrl(), rule.getDefinition());
        });
        
        factoryBean.setFilterChainDefinitionMap(chains);
    }
}

最佳实践:建议配合缓存使用,避免每次请求都查询数据库

3. 自定义Realm实现

public class MyRealm extends AuthorizingRealm {
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken token) throws AuthenticationException {
        
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        
        // 查询用户信息
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("用户不存在");
        }
        
        // 返回认证信息(包含凭证和盐值)
        return new SimpleAuthenticationInfo(
            user.getUsername(), 
            user.getPassword(),
            ByteSource.Util.bytes(user.getSalt()),
            getName()
        );
    }
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
        PrincipalCollection principals) {
        
        String username = (String) principals.getPrimaryPrincipal();
        User user = userService.findByUsername(username);
        
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 添加角色
        info.setRoles(user.getRoles());
        // 添加权限
        info.setStringPermissions(user.getPermissions());
        
        return info;
    }
}

关键点

  • 认证方法doGetAuthenticationInfo负责验证用户身份
  • 授权方法doGetAuthorizationInfo负责加载用户权限
  • 建议将密码比较逻辑交给Shiro处理(通过CredentialsMatcher)

四、扩展点实践

1. 自定义JWT Token支持

public class JWTToken implements AuthenticationToken {
    private String token;
    
    public JWTToken(String token) {
        this.token = token;
    }
    
    @Override
    public Object getPrincipal() {
        return JWTUtil.parseUsername(token);
    }
    
    @Override
    public Object getCredentials() {
        return token;
    }
}

// 配套的Realm
public class JWTRealm extends AuthorizingRealm {
    
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(...) {
        String jwt = (String) token.getCredentials();
        // 验证JWT签名、有效期等
        if (!JWTUtil.verify(jwt)) {
            throw new AuthenticationException("Invalid token");
        }
        return new SimpleAuthenticationInfo(username, jwt, getName());
    }
    
    // ... 授权方法同上
}

2. 集成Redis缓存

public class RedisCacheManager implements CacheManager {
    
    @Override
    public <K, V> Cache<K, V> getCache(String name) {
        return new RedisCache<>(name, redisTemplate);
    }
}

public class RedisCache<K, V> implements Cache<K, V> {
    private final String cacheKey;
    private final RedisTemplate<K, V> redisTemplate;
    
    public RedisCache(String name, RedisTemplate<K, V> redisTemplate) {
        this.cacheKey = "shiro:cache:" + name;
        this.redisTemplate = redisTemplate;
    }
    
    @Override
    public V get(K key) {
        return redisTemplate.opsForHash().get(cacheKey, key);
    }
    
    @Override
    public V put(K key, V value) {
        redisTemplate.opsForHash().put(cacheKey, key, value);
        return value;
    }
    
    // 实现其他必要方法...
}

性能建议

  • 为认证和授权信息设置不同的过期时间
  • 对高频访问的权限数据启用缓存
  • 实现缓存清理机制(如权限变更时)

五、总结图表

图1

通过本文介绍的各种配置方式和实践示例,开发者可以快速掌握Shiro在项目中的常规用法。建议根据实际项目需求选择合适的集成方案,并重点关注认证授权流程的安全性和性能优化。

评论已关闭