Java Apache Shiro权限管理框架配置与自定义Realm

Java Apache Shiro权限管理框架配置与自定义Realm讲座

一、引言

大家好,欢迎来到今天的讲座。今天我们来聊聊Java中非常流行的权限管理框架——Apache Shiro。如果你是第一次听说Shiro,不要担心,我会用轻松诙谐的语言,带你一步步了解这个强大的工具,并教你如何配置它以及自定义Realm。我们还会引用一些国外的技术文档,确保你不仅能理解概念,还能掌握实际操作。

二、什么是Apache Shiro?

首先,让我们了解一下什么是Apache Shiro。Shiro(日语中“城”的意思)是一个强大而灵活的开源安全框架,它可以帮助开发者轻松地实现身份验证、授权、加密和会话管理等功能。Shiro的核心设计理念是简单易用,同时提供足够的灵活性来满足复杂的应用需求。

Shiro的主要功能包括:

  1. 身份验证(Authentication):确认用户的身份,确保他们是合法用户。
  2. 授权(Authorization):控制用户可以访问哪些资源或执行哪些操作。
  3. 加密(Cryptography):提供加密和解密功能,保护敏感数据。
  4. 会话管理(Session Management):管理用户的会话信息,无论是在Web应用还是非Web应用中。
  5. 记住我(Remember Me):允许用户在关闭浏览器后仍然保持登录状态。

三、为什么选择Shiro?

在Java世界里,有很多权限管理框架可以选择,比如Spring Security、JSecurity(Shiro的前身)等。那么,为什么我们要选择Shiro呢?以下是几个理由:

  1. 简单易用:Shiro的API设计非常直观,学习曲线平缓,适合初学者快速上手。
  2. 灵活性高:Shiro提供了丰富的扩展点,允许开发者根据自己的需求进行定制。
  3. 轻量级:相比其他框架,Shiro的依赖较少,启动速度快,占用资源少。
  4. 社区活跃:Shiro有一个庞大的开发者社区,遇到问题时可以很容易找到解决方案。
  5. 广泛支持:Shiro不仅支持Web应用,还支持桌面应用、命令行应用等多种场景。

四、Shiro的基本概念

在深入讲解配置和自定义Realm之前,我们需要先了解一些Shiro的基本概念。这些概念是理解和使用Shiro的基础。

  1. Subject:代表当前用户或客户端。每个Subject都可以进行身份验证和授权操作。Shiro通过Subject类来管理用户的状态。

    Subject currentUser = SecurityUtils.getSubject();
  2. Principal:表示用户的身份标识,通常是用户名或用户ID。一个Subject可以有多个Principal。

  3. Realm:Realm是Shiro与外部安全数据源(如数据库、LDAP、文件等)之间的桥梁。它负责从数据源中获取用户的身份信息和权限信息。你可以将Realm理解为Shiro的安全策略库。

  4. AuthenticationToken:用于传递用户提供的凭证(如用户名和密码),以便进行身份验证。最常见的AuthenticationTokenUsernamePasswordToken

  5. Permission:表示用户可以执行的操作或访问的资源。权限通常以字符串的形式表示,例如user:create表示用户可以创建新用户。

  6. Role:角色是一组权限的集合。一个用户可以拥有多个角色,每个角色可以包含多个权限。

  7. Session:Shiro的会话管理功能允许你在不同类型的环境中管理用户的会话信息。无论是Web应用还是非Web应用,Shiro都能提供一致的会话管理API。

五、Shiro的配置方式

Shiro的配置非常灵活,支持多种配置方式。我们可以使用XML、YAML、Java代码等方式来配置Shiro。为了让大家更好地理解,我们将重点介绍最常用的两种配置方式:基于shiro.ini的配置和基于Java代码的配置。

1. 基于shiro.ini的配置

shiro.ini是Shiro默认的配置文件格式。它使用简单的键值对语法,易于阅读和维护。以下是一个典型的shiro.ini配置示例:

[main]
# 配置Realm
myRealm = com.example.MyCustomRealm
securityManager.realm = $myRealm

# 配置Session管理器
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager

[users]
# 定义用户及其密码
admin = admin123, admin
user = user123, user

[roles]
# 定义角色及其权限
admin = *
user = user:*

[urls]
# 定义URL的访问控制规则
/admin/** = authc, roles[admin]
/user/** = authc, roles[user]
/** = anon

在这个配置文件中,我们定义了一个自定义的Realm MyCustomRealm,并设置了两个用户(adminuser)及其对应的密码和角色。我们还定义了URL的访问控制规则,确保只有具有相应角色的用户才能访问特定的URL。

2. 基于Java代码的配置

除了使用shiro.ini,我们还可以通过Java代码来配置Shiro。这种方式更加灵活,适合需要动态配置的场景。以下是一个基于Java代码的Shiro配置示例:

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    @Bean
    public Realm myRealm() {
        // 返回自定义的Realm实例
        return new MyCustomRealm();
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(securityManager);

        // 定义过滤器链
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/admin/**", "authc, roles[admin]");
        filterChainDefinitionMap.put("/user/**", "authc, roles[user]");
        filterChainDefinitionMap.put("/**", "anon");

        filter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return filter;
    }
}

在这个配置类中,我们通过Spring的@Configuration注解来定义Shiro的Bean。我们创建了一个自定义的MyCustomRealm,并将其设置为SecurityManager的Realm。然后,我们使用ShiroFilterFactoryBean来定义URL的访问控制规则。

六、自定义Realm

接下来,我们来探讨如何自定义Realm。自定义Realm是Shiro的核心功能之一,它允许你根据自己的业务需求,从不同的数据源中获取用户的身份信息和权限信息。下面我们将详细介绍如何创建一个自定义的Realm。

1. 实现Realm接口

要创建一个自定义的Realm,你需要实现org.apache.shiro.realm.Realm接口。这个接口定义了三个核心方法:

  • getAuthenticationInfo(AuthenticationToken token):用于验证用户的身份信息。
  • getAuthorizationInfo(PrincipalCollection principals):用于获取用户的权限信息。
  • supports(AuthenticationToken token):用于判断当前Realm是否支持给定的AuthenticationToken

以下是一个简单的自定义Realm示例:

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class MyCustomRealm extends AuthorizingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());

        // 模拟从数据库中查询用户信息
        if ("admin".equals(username) && "admin123".equals(password)) {
            return new SimpleAuthenticationInfo(username, password, getName());
        } else {
            throw new AuthenticationException("Invalid username or password");
        }
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();

        // 模拟从数据库中查询用户的角色和权限
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if ("admin".equals(username)) {
            info.addRole("admin");
            info.addStringPermission("user:create");
            info.addStringPermission("user:delete");
        } else if ("user".equals(username)) {
            info.addRole("user");
            info.addStringPermission("user:view");
        }

        return info;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }
}

在这个自定义Realm中,我们重写了doGetAuthenticationInfodoGetAuthorizationInfo方法。doGetAuthenticationInfo方法用于验证用户的用户名和密码,而doGetAuthorizationInfo方法用于获取用户的角色和权限。我们还实现了supports方法,确保这个Realm只支持UsernamePasswordToken类型的认证令牌。

2. 使用自定义Realm

一旦你创建了自定义Realm,接下来就是将其集成到Shiro的配置中。我们已经在前面的shiro.ini和Java配置中展示了如何配置自定义Realm。这里再简单回顾一下:

  • shiro.ini中,你可以通过以下方式配置自定义Realm:

    [main]
    myRealm = com.example.MyCustomRealm
    securityManager.realm = $myRealm
  • 在Java代码中,你可以通过以下方式配置自定义Realm:

    @Bean
    public Realm myRealm() {
      return new MyCustomRealm();
    }
    
    @Bean
    public SecurityManager securityManager() {
      DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
      manager.setRealm(myRealm());
      return manager;
    }

七、Shiro的高级功能

除了基本的身份验证和授权功能,Shiro还提供了一些高级功能,帮助你构建更复杂的安全机制。下面我们来介绍其中的几个重要功能。

1. 记住我(Remember Me)

Shiro的“记住我”功能允许用户在关闭浏览器后仍然保持登录状态。要启用这个功能,你需要在shiro.ini中添加以下配置:

[main]
rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager
securityManager.rememberMeManager = $rememberMeManager

rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie
rememberMeCookie.name = rememberMe
rememberMeCookie.httpOnly = true
rememberMeManager.cookie = $rememberMeCookie

# 启用记住我功能
authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.rememberMeParameter = rememberMe

在Java代码中,你可以通过以下方式启用“记住我”功能:

@Bean
public RememberMeManager rememberMeManager() {
    CookieRememberMeManager manager = new CookieRememberMeManager();
    manager.setCipherKey(Base64.decode("kPH+bIxk5D2deZLVgyloOw=="));
    return manager;
}

@Bean
public Cookie rememberMeCookie() {
    SimpleCookie cookie = new SimpleCookie("rememberMe");
    cookie.setHttpOnly(true);
    return cookie;
}

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setRememberMeManager(rememberMeManager());
    return manager;
}
2. 多Realm支持

Shiro支持多Realm配置,这意味着你可以同时使用多个Realm来验证用户的身份信息。这对于需要从多个数据源(如数据库、LDAP、文件等)获取用户信息的场景非常有用。要启用多Realm支持,你只需要在shiro.ini中定义多个Realm,并将它们添加到SecurityManager中:

[main]
realm1 = com.example.Realm1
realm2 = com.example.Realm2
securityManager.realms = $realm1, $realm2

在Java代码中,你可以通过以下方式配置多Realm:

@Bean
public Collection<Realm> realms() {
    List<Realm> realmList = new ArrayList<>();
    realmList.add(new Realm1());
    realmList.add(new Realm2());
    return realmList;
}

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setRealms(realms());
    return manager;
}
3. 会话管理

Shiro提供了强大的会话管理功能,允许你在不同的环境中管理用户的会话信息。你可以通过SessionManager来配置会话的超时时间、存储方式等。以下是一个简单的会话管理配置示例:

[main]
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager

# 设置会话超时时间为30分钟
sessionManager.globalSessionTimeout = 1800000

在Java代码中,你可以通过以下方式配置会话管理:

@Bean
public SessionManager sessionManager() {
    DefaultWebSessionManager manager = new DefaultWebSessionManager();
    manager.setGlobalSessionTimeout(1800000); // 30分钟
    return manager;
}

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setSessionManager(sessionManager());
    return manager;
}

八、总结

通过今天的讲座,我们深入了解了Apache Shiro权限管理框架的配置和自定义Realm。我们从Shiro的基本概念入手,逐步介绍了如何使用shiro.ini和Java代码来配置Shiro,以及如何创建自定义的Realm来满足复杂的业务需求。最后,我们还探讨了一些Shiro的高级功能,如“记住我”、多Realm支持和会话管理。

希望今天的讲座能帮助你更好地理解和使用Apache Shiro。如果你有任何问题或建议,欢迎随时提问。谢谢大家的参与,期待下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注