Apache Shiro:【1】Shiro基础及Web集成

时间:2022-09-22 16:47:08

Apache Shiro:【1】Shiro基础及Web集成

Apache Shiro是什么

  Apache Shiro是一个强大且易于使用的Java安全框架提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。shiro属于轻量级框架,相对于security简单的多。它可以适用于命令行程序、移动开发以至大型的企业级Web应用。

  Shiro提供了安全的API来执行下面几个方面(应用安全的四个基础)的操作:

  • 认证:证实用户身份,经常被称作用户登录。
  • 授权:进行访问控制  
  • 加密:保护和隐藏数据以防窥探。
  • 会话管理:管理每个用户的会话状态。

Apahce Shiro的核心概念

现在我们已经介绍了Shiro的好处,让我们直接进入它的API,这样你就能感受到它。 Shiro的架构有三个主要概念 - Subject,SecurityManager和Realms。

Subject  

  当您保护您的应用程序时,可能最自问的问题是“谁是当前用户?”或“当前用户是否允许执行X”?在我们编写代码或设计用户界面时,我们常常会问自己这些问题:应用程序通常是基于用户故事构建的,并且您希望基于每个用户来表示(和保护)功能。因此,我们在应用程序中考虑安全性的最自然方式是基于当前用户。 Shiro的API在其主题概念中从根本上代表了这种思维方式。
  Subject这个词是一个安全术语,基本上是指“当前正在执行的用户”。它只是不被称为“用户”,因为“用户”这个词通常与人类有关。在安全领域,术语“主体”可以指人类,也可以指第三方流程,守护程序帐户或任何类似的东西。它只是意味着“当前与软件交互的东西”。但是,对于大多数意图和目的,您可以将其视为Shiro的“用户”概念。您可以在代码中的任何位置轻松获取Shiro Subject,如下面的清单1所示。
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();  

  获得Subject后,您可以立即访问当前用户希望使用Shiro执行的所有操作的90%,例如登录,注销,访问其会话,执行授权检查等等 - 但稍后会详细介绍。这里的关键点是Shiro的API在很大程度上是直观的,因为它反映了开发人员在“每个用户”安全控制中思考的自然趋势。在代码中的任何位置访问主题也很容易,允许在任何需要的地方进行安全操作

SecurityManager

  Subject的“幕后”对应物是SecurityManager。当Subject表示当前用户的安全操作时,SecurityManager管理所有用户的安全操作。它是Shiro架构的核心,充当一种“伞形”对象,引用许多形成对象图的内部嵌套安全组件。但是,一旦配置了SecurityManager及其内部对象图,通常会将其保留,应用程序开发人员几乎将所有时间花在Subject API上。  那么如何设置SecurityManager?那么,这取决于您的应用程序环境。例如,Web应用程序通常会在web.xml中指定Shiro Servlet Filter,并设置SecurityManager实例。如果您正在运行独立应用程序,则需要以另一种方式进行配置。但是有很多配置选项。

  每个应用程序几乎总是有一个SecurityManager实例。 它本质上是一个应用程序单例(尽管它不需要是静态单例)。 与Shiro中的几乎所有内容一样,默认的SecurityManager实现是POJO,可以使用任何兼容POJO的配置机制进行配置 - 普通Java代码,Spring XML,YAML,.properties和.ini文件等。基本上任何能够实例化类的东西 并且可以使用调用JavaBeans兼容的方法。
  为此,Shiro通过基于文本的INI配置提供默认的“公分母”解决方案。 INI易于阅读,易于使用,并且需要很少的依赖性。 您还将看到,通过对对象图导航的简单理解,可以有效地使用INI来配置像SecurityManager这样的简单对象图。 请注意,Shiro还支持Spring XML配置和其他替代方案,但我们将在此处介绍INI。
  
[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
# Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false

iniRealm.credentialsMatcher = $cm

[users]
jdoe = TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJpcyByZWFzb2
asmith = IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbXNoZWQsIG5vdCB 
  我们将看到用于配置SecurityManager实例的示例INI配置。 有两个INI部分:[main]和[users]。[main]部分用于配置SecurityManager对象和/或SecurityManager使用的任何对象(如Realms)。 在此示例中,我们看到正在配置两个对象:
  • cm对象,是Shiro的HashedCredentialsMatcher类的一个实例。 如您所见,cm实例的各种属性正在通过'嵌套点'语法(清单3中所示的IniSecurityManagerFactory使用的约定)来配置,以表示对象图导航和属性设置。
  • iniRealm对象,它是SecurityManager用于表示以INI格式定义的用户帐户的组件。
   您可以在[users]部分指定用户帐户的静态列表 - 方便简单应用程序或测试时。出于本简介的目的,理解每个部分的复杂性并不重要,而是要看INI配置是配置Shiro的一种简单方法。 有关INI配置的更多详细信息,请参阅Shiro的文档。
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;

...

//1. Load the INI configuration
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");

//2. Create the SecurityManager
SecurityManager securityManager = factory.getInstance();

//3. Make it accessible
SecurityUtils.setSecurityManager(securityManager);

  在上面代码中,我们在这个简单的示例中看到了一个包含三个步骤的过程:

  • 加载将配置SecurityManager及其组成组件的INI配置。
  • 根据配置创建SecurityManager实例(使用代表工厂方法设计模式的Shiro工厂概念)。
  • 使应用程序可以访问SecurityManager单例。 在这个简单的示例中,我们将其设置为VM静态单例,但这通常不是必需的 - 您的应用程序配置机制可以确定您是否需要使用静态内存。

Realms

  Shiro的第三个也是最后一个核心概念是一个领域。 Realm充当Shiro与应用程序安全数据之间的“桥接”或“连接器”。也就是说,当实际与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个领域中查找许多这些内容
  从这个意义上讲,Realm本质上是一个特定于安全性的DAO:它封装了数据源的连接细节,并根据需要使相关数据可用于Shiro。配置Shiro时,必须至少指定一个Realm用于身份验证和/或授权。可以配置多个Realm,但至少需要一个
  Shiro提供了开箱即用的领域,可以连接到许多安全数据源(也称为目录),如LDAP,关系数据库(JDBC),文本配置源(如INI和属性文件等)。如果默认域不符合您的需要,您可以插入自己的Realm实现来表示自定义数据源。下面的清单4是配置Shiro(通过INI)将LDAP目录用作应用程序领域之一的示例。

[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5

  现在我们已经了解了如何设置基本的Shiro环境,让我们讨论一下,作为开发人员,您将如何使用该框架。

认证

  身份验证是验证用户身份的过程。 也就是说,当用户使用应用程序进行身份验证时,他们证明他们实际上是他们所说的人。 这有时也被称为“登录”。 这通常是一个三步过程。

  • 收集用户的标识信息,称为主体,并支持身份证明,称为凭证。
  • 将主体和凭据提交给系统。
  • 如果提交的凭据与系统对该用户标识(主体)的期望值匹配,则认为该用户已通过身份验证。 如果它们不匹配,则不会将用户视为已通过身份验证。

  每个人都熟悉的这个过程的一个常见例子是用户名/密码组合。 当大多数用户登录软件应用程序时,他们通常会提供用户名(主体)和支持密码(凭证)。 如果存储在系统中的密码(或其表示)与用户指定的密码匹配,则认为它们已经过身份验证。

  Shiro以简单直观的方式支持相同的工作流程。 正如我们所说,Shiro有一个以Subject为中心的API - 几乎所有你在运行时用Shiro做的事都是通过与当前正在执行的Subject进行交互来实现的。 因此,要登录Subject,只需调用其登录方法,传递一个AuthenticationToken实例,该实例表示提交的主体和凭据(在本例中为用户名和密码)。 此示例如下面所示。

//1. Acquire submitted principals and credentials:
AuthenticationToken token =
new UsernamePasswordToken(username, password);

//2. Get the current Subject:
Subject currentUser = SecurityUtils.getSubject();

//3. Login:
currentUser.login(token);

  如您所见,Shiro的API很容易反映出常见的工作流程。 您将继续将此简单性视为所有Subject操作的主题。 调用login方法时,SecurityManager将接收AuthenticationToken并将其分派给一个或多个已配置的Realms,以允许每个Realms根据需要执行身份验证检查。 每个领域都能够根据需要对提交的AuthenticationTokens做出反应。 但是如果登录尝试失败会发生什么? 如果用户指定了错误的密码该怎么办? 您可以通过对Shiro的运行时AuthenticationException作出反应来处理故障,如下所示。

//3. Login:
try {
    currentUser.login(token);
} catch (IncorrectCredentialsException ice) { …
} catch (LockedAccountException lae) { …
}
…
catch (AuthenticationException ae) {…
}

  您可以选择捕获其中一个AuthenticationException子类并进行具体反应,或者通常处理任何AuthenticationException(例如,向用户显示通用的“不正确的用户名或密码”消息)。 根据您的应用要求,您可以选择。
  在成功登录主题后,它们将被视为已通过身份验证,通常您允许它们使用您的应用程序。 但仅仅因为用户证明了他们的身份并不意味着他们可以在您的应用程序中做任何他们想做的事情。 这引出了下一个问题,“我如何控制允许用户做什么?”决定允许用户做什么称为授权。 我们将介绍Shiro如何启用授权

授权

  授权本质上是访问控制 - 控制用户可以在应用程序中访问的内容,例如资源,网页等。大多数用户通过使用角色和权限等概念来执行访问控制。 也就是说,通常允许用户基于分配给他们的角色和/或许可来做某事或不做某事。 然后,您的应用程序可以根据对这些角色和权限的检查来控制公开的功能。 正如您所料,Subject API允许您非常轻松地执行角色和权限检查。 例如,如下代码片段显示了如何检查Subject是否已分配了某个角色。

if ( subject.hasRole(“administrator”) ) {
    //show the ‘Create User’ button
} else {
    //grey-out the button?
}

  如您所见,您的应用程序可以根据访问控制检查启用或禁用功能。
  权限检查是执行授权的另一种方式。 如上例所示检查角色会遇到一个重大缺陷:您无法在运行时添加或删除角色。 您的代码使用角色名称进行了硬编码,因此如果更改了角色名称和/或配置,您的代码就会被破坏! 如果您需要能够在运行时更改角色的含义,或者根据需要添加或删除角色,则必须依赖其他内容。
  为此,Shiro支持其权限概念。 权限是一个原始的功能声明,例如“打开一扇门”,“创建一个博客条目”,“删除'jsmith'用户'等。通过拥有反映应用程序原始功能的权限,您只需要更改权限 更改应用程序的功能时检查。 反过来,您可以在运行时根据需要为角色或用户分配权限

  作为一个例子,如下面所示,我们可以重写之前的角色检查,而是使用权限检查。

if ( subject.isPermitted(“user:create”) ) {
    //show the ‘Create User’ button
} else {
    //grey-out the button?
}

  这样,任何分配了“user:create”权限的角色或用户都可以单击“创建用户”按钮,这些角色和分配甚至可以在运行时更改,为您提供非常灵活的安全模型。
  “user:create”字符串是遵循某些解析约定的权限字符串的示例。 Shiro通过WildcardPermission开箱即用,支持这种约定。 虽然超出了本介绍文章的范围,但您会看到WildcardPermission在创建安全策略时非常灵活,甚至支持实例级访问控制等内容。  

if ( subject.isPermitted(“user:delete:jsmith”) ) {
    //delete the ‘jsmith’ user
} else {
    //don’t delete ‘jsmith’
}

  此示例显示,如果需要,您甚至可以控制对非常细粒度的实例级别的访问。 如果您愿意,您甚至可以创建自己的权限语法。 有关更多信息,请参阅Shiro权限文档。 最后,就像身份验证一样,上述调用最终会转到SecurityManager,后者将咨询一个或多个Realms以做出访问控制决策。 这允许Realm根据需要响应身份验证和授权操作。
  这是对Shiro授权功能的简要概述。 虽然大多数安全框架都停止了身份验证和授权,但Shiro提供了更多功能。 接下来我们将讨论Shiro的高级会话管理功能。  

会话管理

  Apache Shiro在安全框架领域提供了独特的功能:可在任何应用程序和任何架构层中使用的一致会话API。也就是说,Shiro为任何应用程序启用了会话编程范例 - 从小型守护程序独立应用程序到最大的集群Web应用程序。这意味着希望使用会话的应用程序开发人员不再需要使用Servlet或EJB容器,否则就不需要它们。或者,如果使用这些容器,开发人员现在可以选择在任何层中使用统一且一致的会话API,而不是servlet或EJB特定的机制。

  但也许Shiro会话最重要的好处之一就是它们与容器无关。这具有微妙但极其强大的含义。例如,让我们考虑会话群集。有多少特定于容器的方法来集群会话以进行容错和故障转移? Tomcat与Jetty的不同之处在于它与Websphere等不同。但是使用Shiro会话,您可以获得与容器无关的集群解决方案。 Shiro的体系结构允许可插入的Session数据存储,例如企业缓存,关系数据库,NoSQL系统等。这意味着您可以配置一次会话群集,无论您的部署环境如何,它都将以相同的方式工作 - Tomcat,Jetty,JEE Server或独立应用程序。无需根据部署应用程序的方式重新配置应用程序。
  Shiro会话的另一个好处是会话数据可以根据需要在客户端技术之间共享。例如,如果需要,Swing桌面客户端可以参与相同的Web应用程序会话 - 如果最终用户同时使用两者,则会很有用。那么如何在任何环境中访问主题会话?有两种主题方法,如下例所示。

Session session = subject.getSession();
Session session = subject.getSession(boolean create);

  如您所见,这些方法在概念上与HttpServletRequest API完全相同。 第一种方法将返回Subject的现有Session,如果还没有,它将创建一个新的并返回它。 第二种方法接受一个布尔参数,该参数确定是否将创建新的Session(如果它尚不存在)。 一旦你获得了主题的会话,你几乎可以使用它与HttpSession相同。 Shiro团队认为HttpSession API对Java开发人员来说是最舒适的,所以我们保留了很多感觉。 当然,最大的区别在于您可以在任何应用程序中使用Shiro Sessions,而不仅仅是Web应用程序。 清单11显示了这种熟悉程度。

Session session = subject.getSession();

session.getAttribute(“key”, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);
...

加密

  加密是隐藏或混淆数据的过程,因此窥探眼睛无法理解它。 Shiro在加密方面的目标是简化并使JDK的加密支持成为可能。
  重要的是要注意密码学一般不是特定于受试者,因此它是Shiro API的一个非特定Subject的区域。 即使没有使用Subject,您也可以在任何地方使用Shiro的加密支持。 Shiro真正关注其加密支持的两个领域是加密哈希(也称为消息摘要)和加密密码。 让我们更详细地看一下这两个。

哈希

  如果您使用过JDK的MessageDigest类,您很快就会意识到使用它有点麻烦。 它有一个笨拙的静态方法基于工厂的API而不是面向对象的API,你*捕获可能永远不需要捕获的已检查异常。 如果您需要十六进制编码或Base64编码消息摘要输出,您可以自己 - 没有标准的JDK支持。 Shiro使用简洁直观的散列API解决了这些问题。

  例如,让我们考虑相对常见的MD5散列文件并确定该散列的十六进制值的情况。 称为“校验和”,在提供文件下载时会定期使用 - 用户可以在下载的文件上执行自己的MD5哈希,并声明其校验和与下载站点上的校验和匹配。 如果它们匹配,则用户可以充分地假设文件在传输中未被篡改。

  如果没有Shiro,您可以尝试这样做:

  • 将文件转换为字节数组。 JDK中没有任何内容可以帮助解决这个问题,因此您需要创建一个帮助方法来打开FileInputStream,使用字节缓冲区,并抛出相应的IOExceptions等。
  • 使用MessageDigest类来散列字节数组,处理相应的异常,如下面的清单12所示。
  • 将散列字节数组编码为十六进制字符。 JDK中没有任何东西可以帮助解决这个问题,因此您需要创建另一个辅助方法,并且可能在实现中使用按位运算和位移。

  对于这么简单和相对常见的事情来说,这是一项重要的工作。 现在,这里是如何与Shiro完全相同的事情。

String hex = new Md5Hash(myFile).toHex();

  当您使用Shiro简化所有工作时,它会变得非常简单和容易理解。 SHA-512哈希和密码的Base64编码同样简单。

String encodedPassword =
    new Sha512Hash(password, salt, count).toBase64();

  您可以看到Shiro简化了散列和编码的程度,在此过程中为您节省了一些理智。

对称加密

  密码是加密算法,可以使用密钥可逆地转换数据。我们使用它们来保证数据安全,特别是在传输或存储数据时,数据特别容易被窥探的时候。
  如果您曾经使用过JDK Cryptography API,特别是javax.crypto.Cipher类,那么您知道它可能是一个令人难以置信的复杂野兽。对于初学者来说,每个可能的密码配置总是由javax.crypto.Cipher的实例表示。需要做公钥/私钥加密吗?您使用密码。需要使用Block Cipher进行流媒体操作吗?您使用密码。需要创建AES 256位密码来保护数据?您使用密码。你明白了。
  您如何创建所需的Cipher实例?您创建了一个复杂的,不直观的以令牌分隔的密码选项字符串,称为“转换字符串”,并将此字符串传递给Cipher.getInstance静态工厂方法。使用此密码选项字符串方法,没有类型安全性来确保您使用有效选项。这也隐含地意味着没有JavaDoc来帮助您理解相关选项。如果您的String配置不正确,即使您知道配置正确,您还需要处理已检查的异常。如您所见,使用JDK Ciphers是一项非常繁琐的任务。这些技术很久以前就已成为Java API的标准,但时代已经发生变化,我们想要一种更简单的方法。
  Shiro试图通过引入其CipherService API来简化加密密码的整个概念。 CipherService是大多数开发人员在保护数据时所需要的:一个简单的,无状态的,线程安全的API,可以在一个方法调用中完整地加密或解密数据。您需要做的就是提供密钥,您可以根据需要进行加密或解密。例如,可以使用256位AES加密,如下面的清单13所示。 
AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);

//create a test key:
byte[] testKey = cipherService.generateNewKey();

//encrypt a file’s bytes:
byte[] encrypted =
    cipherService.encrypt(fileBytes, testKey);

与JDK的Cipher API相比,Shiro示例更简单:

  您可以直接实例化CipherService - 没有奇怪或令人困惑的工厂方法。
       密码配置选项表示为兼容JavaBeans的getter和setter - 没有奇怪且难以理解的“转换字符串”。
       加密和解密在单个方法调用中执行。
       没有强制检查异常。 如果你愿意,可以抓住Shiro的CryptoException。

Web支持

  最后,但并非最不重要的是,我们将简要介绍Shiro的网络支持。 Shiro附带强大的Web支持模块,可帮助保护Web应用程序。 为Web应用程序设置Shiro很简单。 唯一需要的是在web.xml中定义Shiro Servlet过滤器。 清单14显示了此代码。

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>
        org.apache.shiro.web.servlet.IniShiroFilter
    </filter-class>
    <!-- no init-param means load the INI config
        from classpath:shiro.ini -->
</filter>

<filter-mapping>
     <filter-name>ShiroFilter</filter-name>
     <url-pattern>/*</url-pattern>
</filter-mapping>

  此过滤器可以读取前面提到的shiro.ini配置,因此无论部署环境如何,您都可以获得一致的配置体验。 配置完成后,Shiro过滤器将过滤每个请求,并确保在请求期间可以访问特定于请求的主题。 由于它会过滤每个请求,因此您可以执行特定于安全性的逻辑,以确保只允许满足特定条件的请求。

指定URL过滤链

  Shiro通过其创新的URL过滤器链接功能支持特定于安全性的过滤规则。 它允许您为任何匹配的URL模式指定ad-hoc过滤器链。 这意味着您可以使用Shiro的过滤机制在实施安全规则(或规则组合)方面具有很大的灵活性 - 远远超过仅在web.xml中定义过滤器的能力。 清单15显示了Shiro INI中的配置代码段。

[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc

  如您所见,Web应用程序可以使用[urls] INI部分。对于每一行,等号左侧的值表示与上下文相关的Web应用程序路径。右侧的值定义了一个Filter链 - 一个有序的逗号分隔的Servlet过滤器列表,用于给定路径执行。每个过滤器都是一个普通的Servlet过滤器,但您在上面看到的过滤器名称(anon,user,perms,authc)是Shiro提供的特殊安全相关过滤器。您可以混合搭配这些安全过滤器,以创建非常自定义的安全体验。您还可以指定您可能拥有的任何其他现有Servlet过滤器。
  与使用web.xml相比,这更好,在哪里定义一个过滤器块,然后是一个单独的断开的过滤器模式块?使用Shiro的方法,可以更容易地看到为给定匹配路径执行的过滤器链。如果您愿意,您可以在web.xml中仅定义Shiro过滤器,并在shiro.ini中定义所有其他过滤器和过滤器链,以获得比web.xml更简洁易懂的过滤器链定义机制。即使您没有使用Shiro的任何安全功能,仅此一个小便利就可以使Shiro值得使用。

JSP标签库

  Shiro还提供了一个JSP标记库,允许您根据当前Subject的状态控制JSP页面的输出。 这有用的一个常见示例是在用户登录后显示“Hello <username>”文本。 但如果他们是匿名的,你可能想要展示别的东西,比如“你好! 立即注册!“相反。下面显示了如何使用Shiro的JSP标记来支持它。

<%@ taglib prefix="shiro"
    uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
    <!-- shiro:principal prints out the Subject’s main
        principal - in this case, a username: -->
    <shiro:principal/>!
</shiro:user>
<shiro:guest>
    <!-- not logged in - considered a guest. Show
        the register link: -->
    ! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p>

  还有其他标记允许您根据他们拥有(或没有)的角色,分配(或未分配)的权限以及是否经过身份验证,记住“记住我”服务或者 匿名客人。
  Shiro支持许多其他特定于Web的功能,例如简单的“记住我”服务,REST和BASIC身份验证,当然还有透明的HttpSession支持,如果您想使用Shiro的本机企业会话。 有关更多信息,请参阅Apache Shiro Web文档。

Web会话管理

  最后,有趣的是指出Shiro对Web环境中的会话的支持。
默认的Http会话
  对于Web应用程序,Shiro默认其会话基础结构使用我们已经习惯的现有Servlet容器会话。也就是说,当您调用方法subject.getSession()和subject.getSession(boolean)时,Shiro将返回由Servlet Container的HttpSession实例支持的Session实例。这种方法的优点在于调用subject.getSession()的业务层代码与Shiro Session实例交互 - 它没有“知识”它正在使用基于Web的HttpSession对象。在跨建筑层保持清洁分离时,这是一件非常好的事情。
Shiro在Web层中的本地会话
  如果您已经在Web应用程序中启用了Shiro的本机会话管理,因为您需要Shiro的企业会话功能(如与容器无关的群集),您当然希望HttpServletRequest.getSession()和HttpSession API与“本机”会话一起使用不是servlet容器会话。如果你不得不重构任何使用HttpServletRequest和HttpSession API的代码来改为使用Shiro的Session API,那将是非常令人沮丧的。 Shiro当然不会指望你这样做。相反,Shiro完全实现了Servlet规范的Session部分,以支持Web应用程序中的本机会话。这意味着无论何时调用相应的HttpServletRequest或HttpSession方法调用,Shiro都会将这些调用委托给其内部本地Session API。最终结果是,即使您正在使用Shiro的“本机”企业会话管理,您也不必更改Web代码 - 这确实是一个非常方便(和必要)的功能。