Shiro被设计用来工作在任意环境,从最简单的命令函应用到最大的企业集群应用.针对不同的使用环境,有好几种不同的配置机制.本章节仅介绍Shiro默认支持的配置机制.
2
更多的配置选项
Shiro的SecurityManager
实现并支持组件全部兼容JavaBean.这就允许Shiro读取任意格式的配置文件,比如xml,yaml,json,groovy Builder markup,等等.
编程式配置
最简单最直接创建SecurityManager的方式就是编程式配置,比如:
Realm realm = // 实例化或者获取Realm实例,稍后讨论Realm.
SecurityManager securityManager = new DefaultSecurityManager(realm);
SecurityUtils.setSecurityManager(securityManager);// 使SecurityMangager实例在整个应用环境下可通过静态方式获取
4
惊不惊喜,意不意外?三行代码就配置了完整的Shiro环境,并且许多应用都可以使用,是不是很简单?(小生老师Q_Q:这里只是为了让学者了解配置的简单过程,不是实际案例)
5
SecurityManager对象图
就像在Shiro结构一章中叙述的那样,Shiro的SecurityManager实现本质上是嵌套特定安全组件的模块化对象图,因为它们同样兼容JavaBean,你可以随意调用组件内部的set和get方法来配置SecurityManager和它的内部对象视图.
比如,如果你想配置SecurityManager实例来使用一个自定义的SessionDao来管理Session,你可以通过setSessionDAO方法直接设置:
DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);
SessionDAO sessionDAO = new CustomSessionDAO();
((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
6
你可以通过直接方法调用来配置SecurityManager的任意部分的对象视图
但是,即使编程式配置很简单,它也并不代表大多数应用中最理想的配置方式,以下是一些原因:
-
它需要你了解并直接实例化接口.如果你不必了解里面的实现不需要找它们就可以使用它们不是更好吗.
-
因为Java是类型安全语言,你必须转换对象为方法里面特定的类型,如此多的强制转换是丑陋的,冗长的,并且这些实现类将紧密地耦合到方法里面.
-
SecurityUtils.setSecurityManager方法调用使securitymanager实例成为一个虚拟机静态单例,对于大多数情况是允许的,但如果在同一个虚拟机上允许多个Shiro应用就会产生问题,如果实例是应用程序单例,而不是静态内存引用,那不是更好吗.
-
每次修改Shiro配置的时候都需要你重新编译你的应用
然而,即使有这些警告,我们通过直接编程来操作方法的方式在内存受限的环境(如智能手机应用程序)中仍然是有价值的.如果你的应用没有运行在内存受限的环境,你会发现基于文本的配置将会更加简单和易读.
7
基于INI文件的配置
大多数应用程序都受益于基于文本的配置,这些配置可以独立于源代码进行修改,甚至使那些不熟悉Shiro的API的人更容易理解
为了确保基于通用文本的配置机制能够在所有环境中以最小的第三方依赖关系工作,Shiro支持INI格式来构建SecurityManager
对象图及其所支持的组件.INI文件易于阅读,简化配置,而且容易设置,符合大多数应用的需要.
通过INI文件创建SecurityManager
下面是两个关于如何通过INI文件配置并创建SecurityManager实例的案例.
使用INI源文件配置SecurityManager
我们可以从INI资源路径中创建具有特定配置的SecurityManager实例.资源路径包括文件系统,classpath,或者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);
9
从INI实例中获取SecurityManager
如果需要,也可以通过org.apache.shiro.config.ini
类以编程的方式构建INI配置.INI类方法类似于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();
// 如果需要可以补充ini实例
...
Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
10
现在我们知道如何通过INI配置文件构建一个SecurityManager,让我们看看如何准确定义Shiro INI配置文件.
INI介绍
INI其实是一种文本配置,通过唯一组名组织结构,键值对配置数据.对于键名,在每个组里面都必须是唯一的,不同组里面的键名可以重复,每一组仍然可以看做是单独的属性定义,这是和Properties文件不同的地方.以井号(#
)或分号(;
)开头的行都代表注释.以下是可以被Shiro理解的组配置案例:
# =======================
# Shiro INI 配置
# =======================
[main]
# 对象和它们的属性在这里被定义
# 比如SecurityManager,Realm等等
# 任何你在SecurityManager需要的都可以
[users]
# 当你只需要几个固定用户使用的简单配置时可以在此配置
[roles]
# 当你只需要很少数量的固定角色的情况时可以在此配置
[urls]
# 此小组被用来管理基于网络应用的安全
# 我们将会在网络章节谈到详细定义细节
11
[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
11
定义一个对象
请考虑下面的[main]
组:
[main]
myRealm = com.company.shiro.realm.MyRealm
...
12
这一行实例化了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");
...
13
这是什么原理呢?原理就是我们假定所有的对象都兼容Java Beans.
实际上,Shiro默认使用apache commons BeanUtils 工具类来完成设置这些属性时的所有繁重工作.因此,尽管INI的值都是文本,BeanUtils知道如何转换String字符串到正确的类型,然后将值应用到对应的JavaBeans set方法.
引用值
如果你要设置的值不是原始值,而是一个对象怎么办?嗯,你可以使用美元符($)来引用已经定义好的示例,比如:
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
myRealm.credentialsMatcher = $sha256Matcher
...
14
先简单定义一个对象并指定此对象的名字为sha256Matcher,然后使用BeanUtils通过调用myRealm.setCredentialsMatcher(sha256Matcher)
方法设置对象属性为指定的对象.
嵌套属性
使用点符号可以设置多层嵌套的对象的属性,比如:
...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
15
通过BeanUtils翻译一下就是下面的Java代码:
securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
16
对象遍历的深度可以任意:object.property1.property2....propertyN.value = blah
BeanUtils属性支持
任何属性关联操作BeanUtils都可以支持.setProperty方法在main组将会工作,包括set/list/map对象的分配.具体查看Apache Commons BeanUtils Website官方文档.
17字节数组值
因为原始字节数组不能以文本格式指定,我们必须使用文本编码字节数组,我们可以将字节数组编码为Base64的字符串或者Hex格式的字符串,默认是Base64格式,因为此格式在文本模式下更短.
# cipherKey属性值是一个字节数组.默认情况下,对应字节数组的属性值
# 赋值时使用的文本值都是Base64格式.
securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
...
18
当然,如果你更喜欢使用Hex编码来赋值也是可以的,只需要加一个0x
前缀即可.
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
19
集合属性
List,Set和Map可以被设置为任意值,无论是直接设置还是嵌套调用都可以.对于List,Set只需指定一组以逗号分隔的值或对象引用即可.
比如,某些Session监听器可以如下配置:
sessionListener1 = com.company.my.SessionListenerImplementation
...
sessionListener2 = com.company.my.other.SessionListenerImplementation
...
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
20
对于Map,你必须指定键值对,每一对之间还是使用逗号分隔,键和值之间使用分号分开:
object1 = com.company.some.Class
object2 = com.company.another.Class
...
anObject = some.class.with.a.Map.property
anObject.mapProperty = key1:$object1, key2:$object2
21
在上面的例子中,通过$object1引用的对象使用键key1即可获取,map.get("key1")将返回object1.你还可以使用其他对象作为键:
anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
...
22
注意事项
顺序问题
上面的ini格式和约定非常方便且易于理解,但它不如其他基于文本/xml的配置机制强大.在使用上述机制时,最重要的是要理解顺序的重要性!
小心
每个对象实例化和每个项赋值都按照它们在[main]组部分中出现的顺序执行,这些行将最终转换为JavaBeans的get/set方法调用,所以这些方法是按照文本定义的顺序调用的.写配置的时候一定要记住这一点.
重写实例
任何对象都可以被稍后在配置中定义的同名新实例重写,例如,第二个myRealm
定义将覆盖第一个:
...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm
...
它将导致myRealm被赋值为com.company.security.DatabaseRealm
对象,而之前的实例永远不会被用到,而且还会被回收.
默认的 SecurityManager
你或许已经注意到在完整实例里面SecurityManager 实例类并没有被定义,同时我们直接就设置了它的嵌套属性:
myRealm = ...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
23
这是因为SecurityManager实例是一个特殊的存在,我们已经为您实例化并准备就绪,因此你不需要实例化的特定SecurityManager.
当然,你也可以使用你自定义的实现,可以使用重写实例小节里面提到的技术:
...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...
24
当然,这很少用到——Shiro的SecurityManager实现是非常可定制的,通常可以配置任何必要的东西,如果你真的需要这样做.
25
[users]
[users]组允许你定义一个描述用户账户的静态set集合.这通常在用户账户极少或者用户账户没有必要在运行时动态创建的情况下非常有用.下面是一个示例:
[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz
26
IniRealm自动配置
只要定义非空的[user]或[roles]部分,就会自动触发org.apache.shiro.realm.text.IniRealm实例的创建,并使其在名为iniRealm
的[main]组部分中可用.您可以像上面描述的任何其他对象一样配置它.
行格式
在[users]组部分里面的每一行都必须满足下面的格式:
username
= password
, roleName1, roleName2, …, roleNameN
- 等号左侧是用户名
- 等号右侧第一个是密码,且必须存在
- 在密码后的所有名字都是角色,且角色名可有可无
密码加密
如果你不希望用户组部分的密码以明文的方式呈现,你可以使用你喜欢的加密算法比如MD5,Sha1,Sha256等,并使用返回的字符串作为密码值.默认情况下,密码值被认定为Hex编码,但可以配置为Base64位编码作为替换.
简单的密码加密
为了节省时间,同时使用最好的体验,你或许想使用Shiro的命令行Hasher,它将散列密码以及任何其他类型的资源.特别方便用来加密[user]
组下的密码
一旦你指定了散列密码值,你必须告诉Shiro他们是加密过的,你可以通过配置[main]组里面iniRealm的属性credentialsMatcher的值,此值只需要实现CredentialsMatcher类即可:
[main]
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
iniRealm.credentialsMatcher = $sha256Matcher
...
[users]
# user1 = sha256-散列-hex-编码密码, 角色1, 角色2, ...
user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ...
28
您可以像配置任何其他对象一样配置CredentialMatcher上的任何属性,以反映哈希方法的实现策略,例如,指定是否使用盐或要执行多少次哈希迭代.请参阅org.apache.shiro.authc.credential.hashedCredentialsMatcher Javadoc,以更好地了解哈希策略而且它们或许对你有用。
例如,如果用户的密码字符串是Base64编码的,而不是默认的Hex编码,则可以指定为:
[main]
sha256Matcher.storedCredentialsHexEncoded = false
29
[roles]
[roles]
组允许你通过在[users]组里面定义的角色关联对应的权限Permissions.同样的,当只有很少角色或者每次运行后角色不再修改就很适合这种情况.下面是示例:
[roles]
# 'admin' 角色拥有所有权限,使用通配符表示 *
admin = *
# 'schwartz'角色可以做任何以lightsaber:开头的事情
schwartz = lightsaber:*
# 'goodguy'角色允许"drive"(行为)"winnebago"(类型)
# 车牌号为"eagle5"(实例特定ID)的车
goodguy = winnebago:drive:eagle5
30
行格式
[roles]组的每一行都必须定义一个角色到权限的映射,就像下面这样:
角色名
= 权限定义1, 权限定义2, …, 权限定义N
权限定义是一个任意的字符串,但大多数人希望便于使用且灵活因此使用的字符串满足org.apache.shiro.authz.permission.WildcardPermission
格式.
查看权限文档获取更多信息,你将受益于它们.
Internal commas
注意如果一个权限名里面包含逗号,我们需要将整个权限用双引号括起来,以避免歧义,比如下面的一个权限示例(不是两个偶):
"printer:5thFloor:print,info"
没有权限的角色
[users]组部分有即可, [roles]组部分可以为空.
[urls]
这一章节的详细内容将在Web章详细展现,敬请期待.
网友评论