【Shiro】 配置

Metadata

title: 【Shiro】 配置
date: 2023-01-19 14:42
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/Shiro/基础
categories:
  - Shiro
keywords:
  - Shiro
description: 【Shiro】 配置

【Shiro】 配置

Shiro 旨在在任何环境中工作,从简单的命令行应用程序到最大的企业集群应用程序。由于环境的这种多样性,因此有许多适合配置的配置机制。本节介绍仅 Shiro 核心支持的配置机制。

Shiro 的SecurityManager实现和所有支持组件都是 JavaBeans 兼容的。这允许使用几乎任何配置格式配置 Shiro,例如常规 Java、XML(Spring、JBoss、Guice 等)、YAML、JSON、Groovy Builder 标记等。

编程配置

创建 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 环境。谈何容易!?

SecurityManager 对象图

正如架构一章中所讨论的,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 配置时,它都要求您重新编译您的应用程序。

然而,即使有这些警告,直接编程操作方法在内存受限的环境中仍然很有价值,例如智能手机应用程序。如果您的应用程序不在内存受限的环境中运行,您会发现基于文本的配置更易于使用和阅读。

INI配置

大多数应用程序反而受益于基于文本的配置,这些配置可以独立于源代码进行修改,甚至让那些不熟悉 Shiro 的 API 的人更容易理解。

为确保通用的基于文本的配置机制可以在所有环境中工作且第三方依赖性最小,Shiro 支持INI 格式来构建SecurityManager对象图及其支持组件。INI 易于阅读、易于配置、设置简单,非常适合大多数应用程序。

从 INI 创建一个 SecurityManager

下面是两个如何基于 INI 配置构建 SecurityManager 的示例。

来自 INI 资源的 SecurityManager

我们可以从 INI 资源路径创建 SecurityManager 实例。资源可以从文件系统、类路径或分别以 、 或 为前缀的file:URLclasspath:获取url:。此示例使用 a从类路径的根目录中Factory提取文件并返回实例:shiro.iniSecurityManager

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.IniSecurityManagerFactory;

...

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

来自 INI 实例的 SecurityManager

如果需要,也可以通过org.apache.shiro.config.Ini类以编程方式构建 INI 配置。Ini 类的功能与 JDK 类类似java.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<SecurityManager> factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

现在我们知道如何SecurityManager从 INI 配置构建,让我们来看看如何定义 Shiro INI 配置。

本节

INI 基本上是一个文本配置,由按唯一命名的部分组织的键/值对组成。密钥仅在每个部分是唯一的,而不是在整个配置中(与 JDK Properties不同)。然而,每个部分都可以看作是一个单独的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 setter 方法。

======= 参考值

如果您需要设置的值不是原始值,而是另一个对象怎么办?那么,您可以使用美元符号 ($) 来引用先前定义的实例。例如:

...
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

集合属性

列表、集合和映射可以像任何其他属性一样设置 - 直接设置或作为嵌套属性设置。对于集合和列表,只需指定一组以逗号分隔的值或对象引用。

比如一些SessionListeners:

sessionListener1 = com.company.my.SessionListenerImplementation
...
sessionListener2 = com.company.my.other.SessionListenerImplementation
...
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2

对于地图,您指定一个以逗号分隔的键值对列表,其中每个键值对由冒号“:”分隔

object1 = com.company.some.Class
object2 = com.company.another.Class
...
anObject = some.class.with.a.Map.property

anObject.mapProperty = key1:$object1, key2:$object2

在上面的例子中,引用的对象$object1将在 String key 下的 map 中key1,即map.get(“key1”)returns 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 的配置机制强大。使用上述机制时要了解的最重要的事情是订单很重要!

覆盖实例

任何对象都可以被稍后在配置中定义的新实例覆盖。因此,例如,第二个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] 部分中可用iniRealm。您可以像上面描述的任何其他对象一样配置它。

行格式

[users] 部分中的每一行都必须符合以下格式:

username= password,角色名1 ,角色名 2 , …,角色名N

等号左边的值为用户名

等号右边的第一个值是用户的密码。需要密码。

密码后的任何逗号分隔值都是分配给该用户的角色名称。角色名称是可选的。

加密密码

如果您不希望 [users] 部分的密码为纯文本,您可以使用您喜欢的散列算法(MD5、Sha1、Sha256 等)对它们进行加密,然后使用生成的字符串作为密码值。默认情况下,密码字符串应为十六进制编码,但可以配置为 Base64 编码(见下文)。

简单安全的密码
为了节省时间和使用最佳实践,您可能希望使用 Shiro 的命令行哈希器,它将哈希密码以及任何其他类型的资源。它对加密 INI[users]密码特别方便。

一旦指定了散列文本密码值,就必须告诉 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.HashedCredentialsMatcher JavaDoc 以更好地理解散列策略以及它们是否对您有用。

例如,如果您的用户密码字符串是 Base64 编码而不是默认的十六进制,您可以指定:

[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格式的字符串,以便于使用和灵活。有关权限以及如何从中受益的更多信息,请参阅权限文档。

内部逗号
请注意,如果一个单独的permissionDefinition需要在内部以逗号分隔(例如printer:5thFloor:print,info),则需要用双引号 (“) 将该定义括起来以避免解析错误:"printer:5thFloor:print,info"

没有权限的角色
如果您有不需要权限关联的角色,则不需要在 [roles] 部分中列出它们(如果您不想的话)。如果角色尚不存在,只需在 [users] 部分定义角色名称就足以创建角色。

[urls]

此部分及其选项在Web章节中进行了描述。