Shiro中的配置详解

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

程序化配置

创建SecurityManager并使其可供应用程序使用的最简单的方法是创建org.apache.shiro.mgt.DefaultSecurityManager,并将其放置在代码中。例如:

Realm realm = //实例化一个 Realm 实例.  我们将在后面讨论Realm .
SecurityManager securityManager = new DefaultSecurityManager(realm);

//将SecurityManager实例放到内存里, 以便整个应用程序可用:
SecurityUtils.setSecurityManager(securityManager);

怎么样,Surprising?在仅仅3行代码之后,您就拥有了适用于绝大部分应用程序的全功能Shiro环境。是不是感觉很简单!?

SecurityManager对象视图

正如上文中所讨论的,Shiro的SecurityManager实现,本质上是嵌套安全组件的模块化对象视图。因为它们也兼容JavaBeans,所以可以调用任何嵌套组件getter和setter方法来配置SecurityManager及其内部对象视图。

例如,如果要将SecurityManager实例配置为使用自定义的SessionDAO来自定义会话管理,则可以使用嵌套的SessionManagersetSessionDAO方法直接设置SessionDAO

...

DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);

SessionDAO sessionDAO = new CustomSessionDAO();

((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
...

直接使用方法调用,您也可以配置SecurityManager的对象视图的任意部分。

但是,虽然它像程序化定制一样简单,却不能代表大多数真实世界应用程序的理想配置。以下几个原因可能是不适合在您的应用程序使用硬编程配置的方式:

  • 它要求您了解并直接实现对象的实例化。
  • 由于Java的类型安全性,您需要将通过get *方法获得的对象转换为其特定的实现。如此多的类型转换在代码显得格外的丑陋冗长,并且跟你的业务代码紧紧耦合在一起。
  • SecurityUtils.setSecurityManager方法调用使得实例化的SecurityManager实例成为JVM静态单例,虽然这样做对于许多应用程序来说已经足够了,但如果在同一JVM上运行多个启用Shiro的应用程序,就会导致一系列问题。
  • 每次要进行Shiro配置更改时,它都要求您重新编译应用程序。

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

INI配置

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

为了确保基于文本的配置方式能够在所有具有最小第三方依赖性的环境中工作,Shiro支持使用INI格式来构建SecurityManager对象视图及其支持组件。 INI易于阅读,易于理解,易于设置,适合绝大多数应用。

从INI创建SecurityManager

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

来自INI资源的SecurityManager

我们可以从INI资源路径创建SecurityManager实例。当分别以file:classpath:url:作为前缀时,可以从文件系统,类路径或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<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);

现在我们已经知道如何从INI配置构建SecurityManager,那么接下来让我们看看如何定义Shiro 的INI配置。

INI部分

INI基本上是一个文本配置,由唯一命名的键/值对组成。键在每个部分(下面的代码中的user,role等)是唯一的,而不是在整个配置上(与JDK属性不同)。但是,每个部分也可以被视为单个属性定义。

# =======================
# Shiro INI 配置
# =======================

[main]
# 对象及其属性在此处定义,
# 例如SecurityManager、realms以及构建SecurityManager所需的任何其他内容

[users]
# 当你只需要部分静态用户配置信息的时候,
# 可以在这里简单的配置

[roles]
# 当你只需要部分静态角色的配置信息的时候,
# 可以在这里简单的配置

[urls]
# “urls”部分用于Web应用程序中基于url的安全性。
# 我们将在Web文档中讨论此部分
[main]部分

[main]部分用于配置应用程序的SecurityManager实例及其他依赖项,例如Realms。

听起来像SecurityManager或其他依赖项之类的对象实例相关配置很难与INI扯上关系,因为INI中我们只能使用名/值对的方式来配置。但是通过对对象视图的一些约定与理解,你会发现你可以利用INI做很多事情。 Shiro用这种方式来实现简单而又简洁明了的配置机制。

我们经常喜欢将这种方法称为“农民工版”的依赖注入,虽然没有完整的Spring / Guice / JBoss XML文件那么强大,但是你会发现它在没有太多复杂性的情况下依然出色的完成了很多工作。

好记性不如烂笔头,下面是一个有效的[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");
...

能猜到它是如何实现的嘛?似乎它是假定所有对象都是JavaBean兼容的POJOs,所以才能实现这样的效果。

在框架底层实现中,默认情况下Shiro在设置这些属性时使用Apache Commons Beanutils来完成所有繁重的工作。因此,尽管ini值是文本,Beanutils依然知道如何将字符串值转换为适当的对象类型,然后调用相应的javaBean的setter方法。

引用属性

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

...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
myRealm.credentialsMatcher = $sha256Matcher
...

$先定位名为sha256Matcher的对象,然后使用BeanUtils在myRealm实例上设置该对象(通过调用myRealm.setCredentialsMatcher(sha256Matcher)方法)。

嵌套属性

在INI文件中的行的等号左侧使用.符号,可以遍历对象视图,以最终到达获取所需设置的对象/属性。例如,下面配置行:

...
securityManager.sessionManager.globalSessionTimeout = 1800000
...

翻译成代码即

securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
字节数组值

因为原始字节数组不能以文本格式来指定,所以我们必须使用字节数组的文本编码。可以将值指定为Base64编码的字符串(默认值)或Hex编码的字符串。默认值为Base64,因为Base64编码需要较少的实际文本来表示值 - 它具有更大的编码字母表,这意味着您的标记更短(文本配置更好一点)。

#  'cipherKey' 属性是字节数组.    
# 默认情况下,所有的字节数组属性都将通过Base64编码后以文本属性显示

securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
...

然而,如果您更喜欢使用十六进制编码,只需在字符串标记前加上0x标记:

securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
集合属性

列表(list),集合(set)和映射(map)可以像任何其他属性一样,直接或作为嵌套属性设置。对于集合和列表,可以使用逗号分隔。

例如,下面是一些SessionListeners:

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

对于map,您可以以逗号分隔指定的键值对列表,其中每个键值对由冒号’:'分隔。

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

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

在上面的示例中,$ object1引用的对象将位于字符串 key key1下的映射中,即map.get(“key1”)返回object1。您也可以使用其他对象作为键:

anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
注意事项
排序问题

上面的INI格式和约定非常方便且易于理解,但它没有其他基于文本/ XML的配置机制那么强大。使用上述机制时最重要的一点是排序很重要!

每个对象的实例化和每个值的分配都按照它们在[main]部分中出现的顺序执行。这些行最终转换为JavaBeans的getter/setter方法调用,因此这些方法的调用顺序跟行中出现的顺序相同!在编写配置时请记住这一点。
覆盖实例

任何对象都可以被稍后在配置中定义的新实例覆盖。例如,第二个myRealm定义将覆盖第一个:

...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm
...

这将导致myRealm引用会成为com.company.security.DatabaseRealm实例,并且前一个实例永远不会使用(垃圾回收)。

默认SecurityManager

您可能已经在上面的完整示例中注意到,我们并没有定义SecurityManager实例的类,而是直接调用对象来设置嵌套属性:

myRealm = ...

securityManager.sessionManager.globalSessionTimeout = 1800000
...

这是因为SecurityManager是一个特殊的实例——它已经为您实例化并准备就绪,因此您不需要在INI中指定要实例化的SecurityManager实现类。

当然,如果您实际上想要指定自己的实现,您可以按照上面的“重写实例”部分中的指定定义您的实现:

...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...
[用户]部分

[用户]部分允许您定义一组静态用户帐户。这在用户帐户数量非常少或不需要在运行时动态创建用户帐户的环境中非常有用。下面是一个例子:

[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz
行格式

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

username = password,roleName1,roleName2,...,roleNameN 
  • 等号左侧的值是用户名
  • 等号右边的第一个值是用户的密码。密码是必需的。
  • 密码后面的逗号分隔的所有值是分配给该用户的角色的名称。角色名称是可选的。
加密密码

如果您不希望[users]部分的密码被他人看见,您可以使用您喜欢的哈希算法(MD5,Sha1,Sha256等)来加密并使用生成的字符串作为密码值。默认情况下,密码字符串应为十六进制编码,但可以配置为Base64编码(见下文)。
一旦指定了hash密码值,就必须告诉Shiro这些是加密的。您可以通过在[main]部分中配置隐式创建的iniRealm 的相应CredentialsMatcher实现来匹配您指定的hash算法:

[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.hashedcredentialMatcherjavadoc,以更好地了解散列策略以及它们是否对您有用。

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

[main]
...
# true = hex, false = base64:
sha256Matcher.storedCredentialsHexEncoded = false
[角色]部分

[角色]部分允许您将权限与[用户]部分中定义的角色关联。同样,这在角色数量较少或不需要在运行时动态创建角色的环境中非常有用。下面是一个例子:

[roles]
# 'admin' 角色拥有所有权限
admin = *
# schwartz' 角色拥有 lightsaber的任何权限:
schwartz = lightsaber:*
#  'goodguy' 角色对带有 'eagle5'牌照(实例id)的winnebago (类型)有着 'drive' (动作) 的权限
goodguy = winnebago:drive:eagle5
行格式

[角色]部分中的每一行必须定义一个角色到权限的键/值映射,格式如下:

rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN

其中permissiondefinition是一个任意字符串,但大多数人都希望使用org.apache.shiro.authz.permission.widcardpermission格式的符合,以方便使用和灵活性。有关权限以及如何从中获取想要的的详细信息,请参阅后文的权限文档。

[访问url]部分

我们将在后文的web章节来详细介绍url部分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值