Shiro 旨在在任何环境中工作,从简单的命令行应用程序到最大的企业集群应用程序。由于这种环境的多样性,有许多适合配置的配置机制。本节介绍仅 Shiro 核心支持的配置机制。
许多配置选项
Shiro 的 |
创建 SecurityManager 并使其可用于应用程序的绝对最简单的方法是创建一个org.apache.shiro.mgt.DefaultSecurityManager
并将其连接到代码中。例如:
- Realm realm = //instantiate or acquire a Realm instance. We'll discuss Realms later.
- SecurityManager securityManager = new DefaultSecurityManager(realm);
-
- //Make the SecurityManager instance available to the entire application via static memory:
- SecurityUtils.setSecurityManager(securityManager);
令人惊讶的是,仅仅 3 行代码之后,您现在就拥有了一个适用于许多应用程序的功能齐全的 Shiro 环境。那有多容易!?
正如架构章节中所讨论的,Shiro 的SecurityManager
实现本质上是嵌套的安全特定组件的模块化对象图。因为它们也是 JavaBeans 兼容的,所以您可以调用任何嵌套组件getter
和setter
方法来配置SecurityManager
对象图及其内部对象图。
例如,如果您想将SecurityManager
实例配置为使用自定义SessionDAO
来自定义Session Management,则可以SessionDAO
直接使用嵌套的 SessionManager 的setSessionDAO
方法进行设置:
- ...
-
- DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);
-
- SessionDAO sessionDAO = new CustomSessionDAO();
-
- ((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
- ...
使用直接方法调用,您可以配置“SecurityManager”对象图的任何部分。
但是,就像程序化定制一样简单,它并不代表大多数现实世界应用程序的理想配置。编程配置可能不适合您的应用程序有几个原因:
它要求您了解并实例化直接实现。如果您不必知道具体的实现以及在哪里可以找到它们,那就更好了。
由于 Java 的类型安全特性,您需要将通过get*
方法获得的对象转换为它们的特定实现。这么多的转换是丑陋的,冗长的,并且将您与实现类紧密耦合。
SecurityUtils.setSecurityManager
方法调用使实例化的实例SecurityManager
成为 VM 静态单例,虽然这对于许多应用程序来说很好,但如果多个启用 Shiro 的应用程序在同一个 JVM 上运行,则会导致问题。如果实例是应用程序单例,而不是静态内存引用,那可能会更好。
每次您想要更改 Shiro 配置时,它都要求您重新编译您的应用程序。
然而,即使有这些警告,直接编程操作方法在内存受限的环境中仍然很有价值,比如智能手机应用程序。如果您的应用程序不在内存受限的环境中运行,您会发现基于文本的配置更易于使用和阅读。
大多数应用程序反而受益于基于文本的配置,这些配置可以独立于源代码进行修改,甚至让那些不熟悉 Shiro API 的人更容易理解。
为确保基于文本的通用配置机制能够在所有环境中以最小的第 3 方依赖项工作,Shiro 支持INI 格式来构建SecurityManager
对象图及其支持组件。INI 易于阅读、易于配置、易于设置并且非常适合大多数应用程序。
下面是两个示例,说明如何基于 INI 配置构建 SecurityManager。
来自 INI 资源的 SecurityManager
我们可以从 INI 资源路径创建 SecurityManager 实例。当分别以、 或为前缀时file:
,可以从文件系统、类路径或 URL 获取资源。此示例使用 a从类路径的根目录提取文件并返回实例:classpath:
url:
Factory
shiro.ini
SecurityManager
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.util.Factory;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.config.IniSecurityManagerFactory;
-
- ...
-
- Factory
factory = new IniSecurityManagerFactory("classpath:shiro.ini"); - SecurityManager securityManager = factory.getInstance();
- SecurityUtils.setSecurityManager(securityManager);
来自 INI 实例的 SecurityManager
如果需要,也可以通过org.apache.shiro.config.Ini类以编程方式构建 INI 配置。Ini 类的功能类似于 JDKjava.util.Properties类,但还支持按节名称进行分段。
例如:
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.util.Factory;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.config.Ini;
- import org.apache.shiro.config.IniSecurityManagerFactory;
-
- ...
-
- Ini ini = new Ini();
- //populate the Ini instance as necessary
- ...
- Factory
factory = new IniSecurityManagerFactory(ini); - SecurityManager securityManager = factory.getInstance();
- SecurityUtils.setSecurityManager(securityManager);
既然我们知道了如何SecurityManager
从 INI 配置构造,让我们来看看究竟如何定义 Shiro INI 配置。
INI 基本上是一个文本配置,由由唯一命名的部分组织的键/值对组成。键仅在每个部分中是唯一的,而不是在整个配置中(与 JDK属性不同)。然而,每个部分都可以被视为一个单一的Properties
定义。
注释行可以以 Octothorpe(# - 也称为“哈希”、“磅”或“数字”符号)或分号(“;”)开头
以下是 Shiro 理解的部分示例:
- # =======================
- # Shiro INI configuration
- # =======================
-
- [main]
- # Objects and their properties are defined here,
- # Such as the securityManager, Realms and anything
- # else needed to build the SecurityManager
-
- [users]
- # The 'users' section is for simple deployments
- # when you only need a small number of statically-defined
- # set of User accounts.
-
- [roles]
- # The 'roles' section is for simple deployments
- # when you only need a small number of statically-defined
- # roles.
-
- [urls]
- # The 'urls' section is used for url-based security
- # in web applications. We'll discuss this section in the
- # Web documentation
[Main]
该[main]
部分是您配置应用程序SecurityManager
实例及其任何依赖项的位置,例如Realm。
使用 INI 配置像 SecurityManager 或其任何依赖项这样的对象实例听起来很困难,我们只能使用名称/值对。但是通过对对象图的一点约定和理解,你会发现你可以做很多事情。Shiro 使用这些假设来实现一个简单但相当简洁的配置机制。
我们经常喜欢将这种方法称为“穷人的”依赖注入,虽然没有成熟的 Spring/Guice/JBoss XML 文件那么强大,但您会发现它可以完成很多工作而没有太多复杂性。当然那些其他的配置机制也是可用的,但它们不是使用 Shiro 所必需的。
只是为了激发您的兴趣,这里是一个有效[main]
配置的示例。我们将在下面详细介绍它,但是您可能会发现仅凭直觉就已经了解了很多正在发生的事情:
- [main]
- sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
-
- myRealm = com.company.security.shiro.DatabaseRealm
- myRealm.connectionTimeout = 30000
- myRealm.username = jsmith
- myRealm.password = secret
- myRealm.credentialsMatcher = $sha256Matcher
-
- securityManager.sessionManager.globalSessionTimeout = 1800000
定义一个对象
考虑以下[main]
部分片段:
- [main]
- myRealm = com.company.shiro.realm.MyRealm
- ...
这一行实例化了一个类型的新对象实例,com.company.shiro.realm.MyRealm
并使该对象在myRealm名称下可用,以供进一步参考和配置。
如果实例化的对象实现了该org.apache.shiro.util.Nameable
接口,则该Nameable.setName
方法将在具有名称值的对象上调用(myRealm
在本例中)。
设置对象属性
原始值
只需使用等号即可分配简单的原始属性:
- ...
- myRealm.connectionTimeout = 30000
- myRealm.username = jsmith
- ...
这些配置行转换为方法调用:
- ...
- myRealm.setConnectionTimeout(30000);
- myRealm.setUsername("jsmith");
- ...
这怎么可能?它假定所有对象都是Java Beans兼容的POJO。
在幕后,Shiro 默认使用 Apache Commons BeanUtils在设置这些属性时完成所有繁重的工作。因此,尽管 INI 值是文本,但 BeanUtils 知道如何将字符串值转换为适当的原始类型,然后调用相应的 JavaBeans 设置器方法。
======= 参考值
如果您需要设置的值不是原始值,而是另一个对象怎么办?好吧,您可以使用美元符号 ($) 来引用先前定义的实例。例如:
- ...
- sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
- ...
- myRealm.credentialsMatcher = $sha256Matcher
- ...
这只是定位由名称sha256Matcher定义的对象,然后使用 BeanUtils 在myRealm实例上设置该对象(通过调用该myRealm.setCredentialsMatcher(sha256Matcher)
方法)。
嵌套属性
使用 INI 线等号左侧的虚线表示法,您可以遍历对象图以获取您想要设置的最终对象/属性。例如,这个配置行:
- ...
- securityManager.sessionManager.globalSessionTimeout = 1800000
- ...
(通过 BeanUtils)翻译成以下逻辑:
securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
图形遍历可以根据需要尽可能深:object.property1.property2….propertyN.value = blah
BeanUtils 属性支持
BeanUtils 支持的任何属性赋值操作。setProperty方法将在 Shiro 的 [main] 部分工作,包括 set/list/map 元素分配。有关更多信息,请参阅Apache Commons BeanUtils 网站和文档。 |
字节数组值
因为不能以文本格式原生指定原始字节数组,所以我们必须使用字节数组的文本编码。这些值可以指定为 Base64 编码字符串(默认值)或十六进制编码字符串。默认值为 Base64,因为 Base64 编码需要较少的实际文本来表示值 - 它具有更大的编码字母表,这意味着您的标记更短(文本配置更好一点)。
- # The 'cipherKey' attribute is a byte array. By default, text values
- # for all byte array properties are expected to be Base64 encoded:
-
- securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
- ...
但是,如果您更喜欢使用十六进制编码,则必须在字符串标记前加上0x
('zero' 'x'):
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
集合属性
Lists、Sets 和 Maps 可以像任何其他属性一样设置 - 直接或作为嵌套属性。对于集合和列表,只需指定以逗号分隔的一组值或对象引用。
例如,一些 SessionListener:
- sessionListener1 = com.company.my.SessionListenerImplementation
- ...
- sessionListener2 = com.company.my.other.SessionListenerImplementation
- ...
- securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
对于 Maps,您指定一个以逗号分隔的键值对列表,其中每个键值对由冒号 ':' 分隔
- object1 = com.company.some.Class
- object2 = com.company.another.Class
- ...
- anObject = some.class.with.a.Map.property
-
- anObject.mapProperty = key1:$object1, key2:$object2
在上面的例子中,被引用的对象$object1
将在 Map 中的 String 键下key1
,即map.get("key1")
返回object1
。您还可以使用其他对象作为键:
- anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
- ...
变量插值
定义值时可以使用变量插值。支持的类型是环境变量、系统属性和常量。
对于常量,使用${const:com.example.YourClass.CONSTANT_NAME}
,对于环境变量和系统属性,使用${ENV_VARIABLE_NAME}
or ${system.property}
。
系统属性和环境变量按该顺序查找。
${const:com.example.YourClass.CONSTANT_NAME:-default_value}
默认值以, 或形式支持${VARIABLE_NAME:-default_value}
,如下所示:
这将被解释为myRealm.connectionTimeout = 3000
没有REALM_CONNECTION_TIMEOUT
定义系统属性或环境变量。
如果没有找到替换,定义将保持不变。
注意事项
订单事项
上面的 INI 格式和约定非常方便和易于理解,但不如其他基于文本/XML 的配置机制强大。使用上述机制时,最重要的是要了解Order Matters!
当心
每个对象实例化和每个值分配都按照它们在 [main] 部分中出现的顺序执行。这些行最终转换为 JavaBeans getter/setter 方法调用,因此这些方法以相同的顺序调用!在编写配置时请记住这一点。 |
覆盖实例
任何对象都可以被稍后在配置中定义的新实例覆盖。因此,例如,第二个myRealm
定义将覆盖第一个:
- ...
- myRealm = com.company.security.MyRealm
- ...
- myRealm = com.company.security.DatabaseRealm
- ...
这将导致myRealm
成为一个com.company.security.DatabaseRealm
实例,并且永远不会使用先前的实例(并收集垃圾)。
默认安全管理器
您可能已经注意到在上面的完整示例中没有定义 SecurityManager 实例的类,我们直接开始设置嵌套属性:
- myRealm = ...
-
- securityManager.sessionManager.globalSessionTimeout = 1800000
- ...
这是因为该securityManager
实例是一个特殊的实例——它已经为您实例化并准备就绪,因此您无需知道具体的SecurityManager
实现类即可实例化。
当然,如果您真的想指定自己的实现,您可以,只需按照上面“覆盖实例”部分中指定的方式定义您的实现:
- ...
- securityManager = com.company.security.shiro.MyCustomSecurityManager
- ...
当然,这很少需要 - Shiro 的 SecurityManager 实现非常可定制,通常可以配置任何必要的东西。您可能想问问自己(或用户列表)是否真的需要这样做。
[users]
该[users]
部分允许您定义一组静态用户帐户。这在用户帐户数量非常少或不需要在运行时动态创建用户帐户的环境中非常有用。这是一个例子:
- [users]
- admin = secret
- lonestarr = vespa, goodguy, schwartz
- darkhelmet = ludicrousspeed, badguy, schwartz
自动 IniRealm
只需定义非空的 [users] 或 [roles] 部分将自动触发org.apache.shiro.realm.text.IniRealm实例的创建,并使其在 name 下的 [main] 部分中可用 |
行格式
[users] 部分中的每一行都必须符合以下格式:
username
= password
,角色名称1 ,角色名称 2 , ...,角色名称N
等号左边的值是用户名
等号右边的第一个值是用户的密码。需要密码。
密码后的任何逗号分隔值都是分配给该用户的角色名称。角色名称是可选的。
加密密码
如果您不希望 [users] 部分密码为纯文本,您可以使用您喜欢的散列算法(MD5、Sha1、Sha256 等)加密它们,但您喜欢并使用生成的字符串作为密码值。默认情况下,密码字符串应为十六进制编码,但可以配置为 Base64 编码(见下文)。
简易安全密码
为了节省时间并使用最佳实践,您可能需要使用 Shiro 的命令行哈希器,它将对密码以及任何其他类型的资源进行哈希处理。 |
一旦你指定了散列文本密码值,你必须告诉 Shiro 这些是加密的。您可以通过将 [main] 部分中隐式创建的配置来使用与您指定的哈希算法相对应iniRealm
的适当实现来做到这一点:CredentialsMatcher
- [main]
- ...
- sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
- ...
- iniRealm.credentialsMatcher = $sha256Matcher
- ...
-
- [users]
- # user1 = sha256-hashed-hex-encoded password, role1, role2, ...
- user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ...
您可以CredentialsMatcher
像任何其他对象一样配置任何属性以反映您的散列策略,例如,指定是否使用加盐或要执行多少次散列迭代。请参阅org.apache.shiro.authc.credential.HashedCredentialsMatcherJavaDoc 以更好地了解散列策略以及它们是否对您有用。
例如,如果您的用户密码字符串是 Base64 编码而不是默认的 Hex,您可以指定:
- [main]
- ...
- # true = hex, false = base64:
- sha256Matcher.storedCredentialsHexEncoded = false
[roles]
该[roles]
部分允许您将权限与 [users] 部分中定义的角色相关联。同样,这在具有少量角色的环境或不需要在运行时动态创建角色的环境中很有用。这是一个例子:
- [roles]
- # 'admin' role has all permissions, indicated by the wildcard '*'
- admin = *
- # The 'schwartz' role can do anything (*) with any lightsaber:
- schwartz = lightsaber:*
- # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
- # license plate 'eagle5' (instance specific id)
- goodguy = winnebago:drive:eagle5
行格式
[roles] 部分中的每一行都必须定义一个角色到权限的键/值映射,格式如下:
rolename
= permissionDefinition1 , permissionDefinition2 , ..., permissionDefinitionN
其中permissionDefinition是任意字符串,但大多数人会希望使用符合org.apache.shiro.authz.permission.WildcardPermission格式的字符串,以便于使用和灵活。有关权限以及如何从中受益的更多信息,请参阅权限文档。
内部逗号
请注意,如果单个权限定义需要在内部用逗号分隔(例如 |
无权限角色
如果您有不需要权限关联的角色,则不需要在 [roles] 部分中列出它们(如果您不想这样做)。如果角色尚不存在,只需在 [users] 部分定义角色名称就足以创建角色。 |
[urls]
此部分及其选项在Web章节中进行了描述。