Apache Shiro Configuration

Shiro被设计成可以在任何环境下工作,从简单的命令行应用到最最庞大的企业级集群应用。由于在这种多样性的环境中,所以它有许多配置机制以适应不同的环境。本节覆盖了所有被Shiro核心支持的配置机制。


多种配置项

Shiro的SecurityManager实现和所有支持的组件都兼容JavaBean。这就允许Shiro几乎可以使用任何配置格式进行配置,比如常规的Java,XML(Spring,JBoss,Guice,等等),YAML,JSON,Groovy生成器标记,还有更多……


Programmatic Configuration

创建一个可用的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 Object Graph

正如在 Architecture(架构)章节讨论的那样,Shiro的SecurityManager实现本质上是一个嵌套了许多安全特性组件的模块化对象图。又因为它们兼容JavaBean,所以你可以调用任何嵌套组件的getter和setter方法来配置SecurityManager及其内部对象图。

例如,如果你想给SecurityManager配置一个定制的SessionDAO来定制 Session Management组件,你可以直接用内嵌的SessionManager的setSessionDAO方法来设置SessionDAO:

...

DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);

SessionDAO sessionDAO = new CustomSessionDAO();

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


直接用方法调用,你可以配置SecurityManager的对象图的任何部分。

但是,尽管程序定制方案很简单,它在大多数现实应用中并不是理想的配置方案。这里有几个理由解释了为什么程序定制方案不适合你的应用:


然而,尽管有这么多的警告,但你在内存受限的环境下使用直接编程的方法还是有价值的,比如只能手机应用。如果你的应用不是运行在一个内存受限的环境下,你会发现基于文本的配置更加易用易读。


INI Configuration

大多数应用通过改用基于文本的配置,而获得这些好处:可以独立于源代码进行修改配置,并且可以让那些不熟悉Shiro API的人更容易理解。

为了确保有一个具有普适性的基于文本的配置机制——也就是说只要最小的第三方依赖就能在所有环境下工作,Shiro支持用INI格式建立起SecurityManager对象图及其支持的组件。INI容易阅读,容易配置,并且安装起来很简单,适合大部分应用。

Creating a SecurityManager from INI

这里有两个例子介绍怎么基于INI配置建立起SecurityManager。

SecurityManager from an INI resource

我们可以通过一个INI资源路径来创建SecurityManager实例,资源可以分别从文件系统,类路径或者URL获取,分别对应前缀file:,classpath: 或者 url: 。这个例子用一个Factory从classpath根路径下获取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);

SecurityManager from an INI instance

INI配置可以通过 org.apache.shiro.config.Ini 类程序化构造,这个INI类的功能和JDK中的  java.util.Properties 类差不多,但是额外支持通过section名称进行分段。

例如:

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 Sections

INI基本上是由一对对键值对组成的文本配置文件,这些键值对由命名唯一的section来组织分类。key只需要在section范围内保证唯一,而不需要在整个配置文件中(这点和JDK的 Properties不一样)。每个section可以被视作就是一个单独的Properties定义。

注释行可以用#号(又名“哈希”、“磅”、“数字”的符号)或者分号(“;”)开头。

这是一个Shiro可以理解的section的例子:

# =======================
# 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] section 是用来配置应用的SecurityManager实例及其依赖的所有其他组件,比如说 Realm

配置像SecurityManager及其依赖的所有其他组件一样的对象实例听起来很难用INI来做到,毕竟我们只能使用键值对。但是通过对对象图的一些约束和理解,你就会发现你其实可以用INI做很多。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


Defining an object

思考一下下面的[main] section 片段:

[main]
myRealm = com.company.shiro.realm.MyRealm
...

这一行配置实例化了一个com.company.shiro.realm.MyRealm类型的对象实例,并且使这个对象可以用 myRealm 这个名字作为进一步的引用和配置。

如果被实例化的对象实现了org.apache.shiro.util.Nameable接口,那么这个对象的Nameable.setName就会被调用,并且方法参数是这个名字的值(这个例子中名字的值是MyRealm)。

Setting object properties

Primitive Values

简单的原始属性可以只用等于号赋值:

...
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
...

这些行的配置转成方法调用是这样子的:

...
myRealm.setConnectionTimeout(30000);
myRealm.setUsername("jsmith");
...

这怎么可能呢?那是因为假定了所有的对象都是JavaBean兼容的POJO。

在后台,当设置这些属性的时候Shiro默认使用 Apache Commons BeanUtils 来做所有的重活,所以尽管INI中的值都是文本,BeanUtils知道怎么把这些字符串转化成合适的原始类型,并且调用相应的JavaBean的setter方法。

Reference Values

如果你要设置的值不是一个原始类型的,而是另外一个对象时该怎么办?嗯,你可以用一个美元符($)去引用一个已经定义好的实例。例如:

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

这样就简单地定位了由sha256Matcher这个名字来定义的对象,然后由BeanUtils在myRealm实例上设置这个对象(通过调用myRealm.setCredentialsMatcher(sha256Matcher)方法)。

Nested Properties

在INI配置行的等号的左边使用点号(.),你可以遍历整个对象图来找到你最终想要设置的对象/属性。例如,这个配置行:

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

转成代码(用BeanUtils)是这样的逻辑:

securityManager.getSessionManager().setGlobalSessionTimeout(1800000);

对图的遍历可以想多深就多深:object.property1.property2....propertyN.value = blah

BeanUtils属性支持

任何支持 BeanUtils.setProperty 方法的属性赋值操作都可以在Shiro的[main] section 工作,包括对set/list/map元素的赋值。参考 Apache Commons BeanUtils Website 及其文档获取更多相关信息。

Byte Array Values

因为原始的字节数组不能在一个文本格式配置文件中被天然地描述,我们必须使用一种字节数组的文本编码。字节数组的值可以指定为一个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:

securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008

Collection Properties

Lists、Sets和Maps可以像任何其他属性一样被设置——不管是直接设置还是被当成一个嵌套属性来设置。对于sets和lists来说,只要指定一个用逗号分隔的值或引用的集合。

例如,配置一些会话监听者:

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

在上面的例子中,在map中被$object1引用的对象将存储在字符串键key1之下,例如,map.get("key1")将返回object1。你也可以用一个对象作为键:

anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
...

Considerations(注意事项)

Order Matters

在上面的描述中,INI的格式和惯例都是非常方便和容易理解的,但是它没有其它基于文本或XML的配置机制那么强大,在使用上述的机制时,最要注意的是配置的顺序会有影响!

小心

每个对象的实例化和每个赋值操作都是在[main] section 下按顺序执行的。这些配置行最终被转化成一条条的JavaBeans getter/setter方法调用,所以这些方法调用的顺序和配置的顺序是一样的!

在你写配置的时候牢记在心。

Overriding Instances

任何一个对象都可以被后面新定义的实例所覆盖,例如,第二个myRealm定义会覆盖第一个:

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

这将导致myRealm是一个com.company.security.DatabaseRealm实例,前面的那个实例将永远不会被用到(JVM会把它当做垃圾回收)。

Default SecurityManager

你可以已经注意到了,在完成上面的例子时,没有定义SecurityManager实例的类,我们直接跳到设置它的嵌套属性:

myRealm = ...

securityManager.sessionManager.globalSessionTimeout = 1800000
...

这是因为securityManager实例是非常特殊的——它早已给你实例化好了,随时可以被使用,所以你不必知道具体的SecurityManager实现类并且去实例化它。

当然,如果你一定要指定你自己的实现,可以,但是请只在“压倒一切的情况下”指定你自己定义的实现:

...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...

当然,这种需求非常之少——Shiro的SecurityManager实现是非常可定制化的,并且通常可以配置上任何你所需要的。你可能会想问自己(或用户列表)是否真需要自己重新实现一个。

[users]

[users0] section允许你定义一组静态的用户账户,这在用户账户数量非常少或者用户账户不需要运行时动态创建的情况下非常有用。这里有一个例子:

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


自动实例化的IniRealm

只要定义了一个非空的[users]或[roles] section,就会自动触发创建一个 org.apache.shiro.realm.text.IniRealm 实例,并且在[main] section 中可以直接引用iniRealm这个变量名。你可以像配置上面描述的其他对象一样配置它。

Line Format

[users] section的每一行配置都必须符合下列格式:
username = password, roleName1, roleName2, ..., roleNameN


Encrypting Passwords

如果你不希望写在[users] section里面的密码是明文,你可以用你最喜欢的哈希算法(MD5, Sha1, Sha256等等)去加密它,你喜欢并且用哈希算法产生的字符串作为密码值。默认情况下,密码字符串被十六进制编码,但是你可以通过配置使用Base64编码(看下面):

轻松保护密码

为了节省时间和使用最佳实践,你可能会想用Shiro的命令行哈希器,它可以像哈希编码其他资源一样对密码进行编码。在加密INI [users] 中的密码时尤为方便。

一旦你指定了被哈希过的密码值,你必须告诉Shiro这些都是加过密的。通过给在[main] section 中隐式创建的iniRealm设置CredentialsMatcher实例,每个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 文档 ,它或许对你有用。

例如,如果你的用户密码字符串采用Base64编码来代替默认的十六进制编码,你可以这样指定:

[main]
...
# true = hex, false = base64:
sha256Matcher.storedCredentialsHexEncoded = false

[roles]

[roles] section允许你把权限和在[users] section中定义的那些角色关联起来。再次重复说明一下,这在角色数量非常少或者角色不需要运行时动态创建的情况下非常有用。这是一个例子:

[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

Line Format

[roles] section的每一行配置都必须定义成“角色-权限(一对多) ”的键值对映射,必须符合下面的格式:
rolename = permissionDefinition1, permissionDefinition2, ..., permissionDefinitionN

尽管permissionDefinition可以是任意的字符串,但是大多数人都倾向于使用遵守 org.apache.shiro.authz.permission.WildcardPermission 格式的字符串,因为它简单易用而且灵活。参考  Permissions  文档获取更多权限相关信息,以及了解你将从中收获什么。

内部的逗号

注意,如果你需要在一个特别的permissionDefinition中使用到内部逗号(e.g. printer:5thFloor:print,info),你需要在外面包一个双引号",以避免解析错误。


没有权限的角色

如果你有那种不需要关联权限的角色,你就干脆不要在[roles] section中写它。只要在[users] section中定义角色名称就可以了。


[urls]

这个section及其选项在Web章节中描述



原文地址