Apache Shiro权限管理深度解析:核心组件、配置与集成、优势及适用场景全面详解

1. Shiro 的核心概念与组件深入剖析

Shiro 的设计围绕几个核心组件展开,这些组件共同协作以实现完整的安全功能。

🌟 Subject(用户主体)

Subject 是 Shiro 中的核心概念之一,表示当前用户(可以是登录的用户或匿名用户)。它是与用户交互的主要接口,提供了对用户身份验证、授权和会话管理的操作支持。

  • 获取当前 Subject

    Subject currentUser = SecurityUtils.getSubject();
    
  • 检查用户是否已登录

    if (currentUser.isAuthenticated()) {
        System.out.println("用户已登录");
    }
    
  • 执行登出操作

    currentUser.logout();
    
  • 获取用户信息
    Subject 可以通过 getPrincipals() 方法获取用户的标识信息(如用户名、ID 等)。

    PrincipalCollection principals = currentUser.getPrincipals();
    String username = (String) principals.getPrimaryPrincipal();
    System.out.println("当前用户: " + username);
    
  • 多线程环境下的 Subject 管理
    在多线程环境中,可以通过 ThreadContextRunAsUtils 来绑定和切换 Subject。

    // 绑定 Subject 到当前线程
    ThreadContext.bind(subject);
    
    // 切换 Subject
    Subject.runAs(new SimplePrincipalCollection("newUser", "realm"));
    
  • 动态切换用户身份
    在某些场景下(如系统管理员模拟其他用户操作),可以通过 runAs 动态切换用户身份。

    Subject currentUser = SecurityUtils.getSubject();
    currentUser.runAs(new SimplePrincipalCollection("admin", "myRealm"));
    

🌟 SecurityManager(安全管理器)

SecurityManager 是 Shiro 的核心组件,负责协调各个子模块(如认证、授权、会话管理)。它是整个 Shiro 安全框架的入口点,所有的安全操作最终都会通过 SecurityManager 来完成。

  • 配置 SecurityManager
    在 Spring Boot 中,可以通过定义 DefaultWebSecurityManager 来设置 SecurityManager

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        return securityManager;
    }
    
  • 缓存管理
    SecurityManager 支持缓存机制,可以显著提升性能。例如,可以通过 EhCacheRedis 配置缓存。

    @Bean
    public CacheManager cacheManager() {
        return new EhCacheManager();
    }
    
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }
    
  • 自定义缓存策略
    可以为不同的 Realm 配置独立的缓存策略。

    @Bean
    public CachingDefaultAuthorizingRealm myRealm() {
        CachingDefaultAuthorizingRealm realm = new MyRealm();
        realm.setAuthorizationCacheName("authorizationCache");
        realm.setAuthenticationCacheName("authenticationCache");
        return realm;
    }
    
  • 全局异常处理
    可以为 SecurityManager 配置全局异常处理器,统一处理认证和授权失败的情况。

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(new ModularRealmAuthenticator());
        securityManager.setAuthorizer(new ModularRealmAuthorizer());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
    

🌟 Realm(领域)

Realm 是 Shiro 与数据源之间的桥梁,用于验证用户身份和获取权限信息。常见的数据源包括数据库、LDAP、文件等。开发者需要自定义 Realm 来适配自己的业务逻辑。

  • 自定义 Realm 示例

    public class MyRealm extends AuthorizingRealm {
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = (String) principals.getPrimaryPrincipal();
    
            // 根据用户名从数据库中获取角色和权限
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRole("admin");
            info.addStringPermission("user:create");
    
            return info;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            String username = upToken.getUsername();
    
            // 模拟从数据库中获取用户信息
            if ("admin".equals(username)) {
                return new SimpleAuthenticationInfo(username, "123456", getName());
            } else {
                throw new UnknownAccountException("未知账户");
            }
        }
    }
    
  • 多 Realm 支持
    如果项目中有多个数据源,可以通过配置多个 Realm 来实现。

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealms(Arrays.asList(new Realm1(), new Realm2()));
        return securityManager;
    }
    
  • 动态加载权限
    在实际项目中,权限可能需要动态加载。可以通过自定义 AuthorizationInfo 来实现。

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
    
        // 动态查询数据库获取权限
        List<String> roles = roleService.getRolesByUsername(username);
        List<String> permissions = permissionService.getPermissionsByUsername(username);
    
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);
    
        return info;
    }
    
  • 密码加密匹配
    在实际项目中,通常需要对用户密码进行加密存储。可以通过 HashedCredentialsMatcher 来实现密码加密匹配。

    @Bean
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256"); // 使用 SHA-256 加密
        matcher.setHashIterations(1024); // 迭代次数
        return matcher;
    }
    
    @Bean
    public MyRealm myRealm() {
        MyRealm realm = new MyRealm();
        realm.setCredentialsMatcher(credentialsMatcher());
        return realm;
    }
    

🌟 SessionManager(会话管理器)

Shiro 提供了强大的会话管理功能,允许开发者在任何环境中(不仅仅是 Web 应用)管理用户的会话状态。通过 subject.getSession() 可以获取用户的会话对象,并对其进行操作。

  • 会话超时设置

    Session session = currentUser.getSession();
    session.setTimeout(1800000); // 设置会话超时时间为 30 分钟
    
  • 分布式会话管理
    在集群环境中,可以通过 Redis 实现分布式会话管理。

    @Bean
    public SessionDAO sessionDAO() {
        return new RedisSessionDAO();
    }
    
    @Bean
    public DefaultSessionManager sessionManager() {
        DefaultSessionManager sessionManager = new DefaultSessionManager();
        sessionManager.setSessionDAO(sessionDAO());
        return sessionManager;
    }
    
  • 会话监听器
    可以通过实现 SessionListener 接口来监听会话的创建、更新和销毁事件。

    public class MySessionListener implements SessionListener {
        @Override
        public void onStart(Session session) {
            System.out.println("会话开始");
        }
    
        @Override
        public void onStop(Session session) {
            System.out.println("会话结束");
        }
    
        @Override
        public void onExpiration(Session session) {
            System.out.println("会话过期");
        }
    }
    
  • 会话持久化
    在分布式环境中,可以通过 Redis 或数据库实现会话持久化。

    @Bean
    public SessionDAO sessionDAO() {
        return new EnterpriseCacheSessionDAO(); // 使用缓存会话 DAO
    }
    

2. Shiro 的权限管理流程深入解析

🌟 身份认证(Authentication)

身份认证是确认用户身份的过程。在 Shiro 中,可以通过以下步骤完成用户登录:

  1. 创建一个 UsernamePasswordToken 对象,包含用户的用户名和密码。
  2. 使用 subject.login(token) 方法尝试登录。
  3. 如果登录成功,则返回 true;如果失败,则抛出相应的异常(如 UnknownAccountExceptionIncorrectCredentialsException)。
  • 完整登录流程示例

    Subject currentUser = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        currentUser.login(token); // 登录操作
        System.out.println("登录成功!");
    } catch (UnknownAccountException e) {
        System.out.println("未知账户: " + e.getMessage());
    } catch (IncorrectCredentialsException e) {
        System.out.println("密码错误: " + e.getMessage());
    } catch (LockedAccountException e) {
        System.out.println("账户被锁定: " + e.getMessage());
    } catch (AuthenticationException e) {
        System.out.println("其他登录异常: " + e.getMessage());
    }
    
  • 记住我功能
    Shiro 支持“记住我”功能,允许用户在关闭浏览器后仍然保持登录状态。

    token.setRememberMe(true);
    
  • 密码加密处理
    在实际项目中,通常需要对用户密码进行加密存储。可以通过 HashedCredentialsMatcher 来实现密码加密匹配。

    @Bean
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256"); // 使用 SHA-256 加密
        matcher.setHashIterations(1024); // 迭代次数
        return matcher;
    }
    
    @Bean
    public MyRealm myRealm() {
        MyRealm realm = new MyRealm();
        realm.setCredentialsMatcher(credentialsMatcher());
        return realm;
    }
    

🌟 授权(Authorization)

授权是指验证用户是否有权访问某些资源或执行某些操作。Shiro 支持基于角色(Role-Based)和基于资源(Permission-Based)的授权方式。

  • 基于角色的授权
    检查用户是否属于某个角色。

    if (currentUser.hasRole("admin")) {
        System.out.println("用户有管理员权限");
    } else {
        System.out.println("用户不是管理员");
    }
    
  • 基于资源的授权
    检查用户是否拥有特定权限。

    if (currentUser.isPermitted("user:create")) {
        System.out.println("用户有创建用户的权限");
    } else {
        System.out.println("用户无创建用户的权限");
    }
    
  • 批量权限检查
    使用 isPermittedAll 方法可以一次性检查多个权限。

    if (currentUser.isPermittedAll("user:create", "user:delete")) {
        System.out.println("用户有创建和删除用户的权限");
    }
    
  • 动态权限控制
    在实际项目中,权限可能需要动态加载。可以通过自定义 PermissionResolver 来实现。

    public class WildcardPermissionResolver implements PermissionResolver {
        @Override
        public Permission resolvePermission(String permissionString) {
            return new WildcardPermission(permissionString);
        }
    }
    

🌟 会话管理

Shiro 提供了独立于 Servlet 的会话管理机制,适用于非 Web 应用场景。通过 subject.getSession() 可以获取用户的会话对象,并对其进行操作。

  • 会话属性管理

    Session session = currentUser.getSession();
    session.setAttribute("key", "value");
    String value = (String) session.getAttribute("key");
    System.out.println("会话值: " + value);
    
  • 会话持久化
    在分布式环境中,可以通过 Redis 或数据库实现会话持久化。

    @Bean
    public SessionDAO sessionDAO() {
        return new EnterpriseCacheSessionDAO(); // 使用缓存会话 DAO
    }
    

3. Shiro 的配置与集成深入解析

🌟 Shiro.ini 配置文件

Shiro 提供了一个简单的 .ini 文件来配置安全规则。以下是一个典型的配置示例:

[users]
# 用户名=密码,角色
admin=123456, admin_role
user=abcd1234, user_role

[roles]
# 角色=权限
admin_role=*       # 拥有所有权限
user_role=read     # 仅拥有读取权限

[urls]
# URL过滤规则
/login = anon      # 登录页面无需认证
/logout = logout   # 登出操作
/** = authc        # 其他页面需要认证
  • 动态 URL 配置
    在大型项目中,URL 规则可能需要动态加载。可以通过实现 FilterChainDefinitionMapLoader 接口来实现。
    public class MyFilterChainDefinitionMapLoader implements FilterChainDefinitionMapLoader {
        @Override
        public Map<String, String> loadFilterChainDefinitions() {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("/login", "anon");
            map.put("/logout", "logout");
            map.put("/**", "authc");
            return map;
        }
    }
    

🌟 Spring Boot 集成

在现代开发中,Spring Boot 是主流的 Java 框架之一。Shiro 可以轻松地与 Spring Boot 集成,具体步骤如下:

  1. 添加依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.9.1</version>
    </dependency>
    
  2. 配置 ShiroFilterFactoryBean
    创建一个 ShiroConfig 类,定义 ShiroFilterFactoryBeanDefaultWebSecurityManager

    @Configuration
    public class ShiroConfig {
    
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
            filterFactoryBean.setSecurityManager(securityManager);
    
            // 定义过滤规则
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            filterChainDefinitionMap.put("/login", "anon"); // 登录页面无需认证
            filterChainDefinitionMap.put("/logout", "logout"); // 登出操作
            filterChainDefinitionMap.put("/**", "authc"); // 其他页面需要认证
            filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
            return filterFactoryBean;
        }
    
        @Bean
        public DefaultWebSecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myRealm());
            return securityManager;
        }
    
        @Bean
        public MyRealm myRealm() {
            return new MyRealm(); // 自定义 Realm
        }
    }
    
  3. 自定义登录页面
    可以通过 ShiroFilterFactoryBean 设置自定义的登录页面。

    filterFactoryBean.setLoginUrl("/custom-login");
    filterFactoryBean.setSuccessUrl("/home");
    filterFactoryBean.setUnauthorizedUrl("/unauthorized");
    

4. Shiro 的优势与适用场景

🌟 优势

  • 简单易用:API 设计直观,适合中小型项目快速实现权限管理。
  • 灵活性高:支持多种数据源(数据库、LDAP 等),并允许开发者自定义 Realm。
  • 跨平台支持:不仅限于 Web 应用,还适用于桌面应用和分布式系统。
  • 强大的会话管理:提供独立于 Servlet 的会话机制,支持集群环境。

🌟 适用场景

  • 需要快速实现身份认证和授权的小型项目。
  • 需要灵活定制权限管理规则的中型项目。
  • 非 Web 应用场景(如桌面应用或微服务架构)。
posted @   软件职业规划  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 35岁程序员的中年求职记:四次碰壁后的深度反思
· 当职场成战场:降职、阴谋与一场硬碰硬的抗争
· 用99元买的服务器搭一套CI/CD系统
· Excel百万数据如何快速导入?
· ShadowSql之.net sql拼写神器
点击右上角即可分享
微信分享提示