总的参考文档:http://shiro.apache.org/reference.html
本文参考文档:http://shiro.apache.org/configuration.html
原文中关于配置的话题是概述中的一节,配置很重要也有点小复杂,这里把它独立出来成为一章。
首先明确一点,在使用Shiro之前要先进行配置,那么配置的目的是什么?配置的东西是什么?首先再看一下Shiro的详细架构图:
重点关注SecurityManager这一块。前文说过,SecurityManager相当于是一个大帽子,里边有很多组件。在进行应用开发时,我们需要实例化一个SecurityManager对象。SecurityManager包含很多组件,在实例化SecurityManager之前,要先实例化Authenticator、Authorizer等各种组件,然后再通过SecurityManager的各种setter方法或者构造函数将Authenticator、Authorizer等组件设置进去,最后完成SecurityManager的初始化。Authenticator、Authorizer等各种组件可以是Shiro内置实现,也可以是开发者自定义的实现。
配置的目的就是控制应用如何始化SecurityManager包含的各种组件以及SecurityManager本身的初始化。
SecurityManager本身及它包含的所有组件都是JAVA BEAN对象,我们可以通过在程序以硬编码的方式实例化SecurityManager及它包含的各种组件,绕过配置文件,当然一般不会这么干。看两个示例。
示例1:
Realm realm = new MyRealm(param1, param2, param3,...);
SecurityManager securityManager = new DefaultSecurityManager(realm);
SecurityUtils.setSecurityManager(securityManager);
上例很简单。
第一行代码:MyRealm是开发者实现Shiro的Realm接口而自定义的Realm,这个相当于是通常应用中的DAO层,先实例化它。
第二行代码:实例化SecurityManager,它是DefaultSecurityManager类型,把第一行实例化的Realm当成参数传给它。
第三行代码:这个主要实现单例模式。把实例化后的SecurityManager传给SecurityUtils,SecurityUtils在整个应用中只有一个实例,setSecurityManager只需要一次,在应用中的其它地方使用SecurityManager时,只需要调用SecurityUtils.getSecurityManager()方法就行了。
SecurityManager包含的其它组件,开发者没有提供自己的实现,那么就用Shiro的默认实现。
示例2:
...
DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);
SessionDAO sessionDAO = new CustomSessionDAO();
((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
...
这个更进一步,它实现了自定义SessionDAO。SessionDAO并不是SecurityManager的直属成员,SecurityManager包含SessionManager,SessionManager才包含SessionDAO,因此它的设置方法就像最后一行代码一样。
硬编码不够灵活,通过配置文件配置JAVA BEAN,程序中的工厂类读取配置文件并根据配置文件实例化JAVA BEAN对象,这个很常见,Shiro也一样。
示例代码如下:
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);
上边的代码简单明了,shiro.ini就是配置文件。前边的classpath前缀表示文件们于应用的classpath路径下。也可以这样指定:file:/目录1/目录2/.../shiro.ini,这个表示配置文件位于文件系统中。还可以这样:uri:http://xxxx:8080/myweb/shiro.ini,通过http请求获取文件。
在实际的项目开发中,SecurityManager的配置信息有可能保存在zookeeper、etcd、各种数据库中。这个时候可以先实例化一个org.apache.shiro.config.Ini类,这个类与java.util.Properties相似,自己写一段代码从各种数据源读配置信息,然后设置到org.apache.shiro.config.Ini里边,最后再将org.apache.shiro.config.Ini实例传给工厂类,或者通过继承覆盖org.apache.shiro.config.Ini中的方法都可以。配置示例如下:
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 factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
就是觉的普通配置文件,以下是完整样例:
# =======================
# 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
内容简单明了,分成四个section。
这个是配置文件的主机部分,主要是用来决定如何初始化SecurityManager实例,SecurityManager本身、SecurityManager的直属组件、SecurityManager直属组件的组件都可以在这里配置,有那些组件可以参考Shiro的详细架构图。先看一个示例:
1 [main]
2 sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
4 myRealm = com.company.security.shiro.DatabaseRealm
5 myRealm.connectionTimeout = 30000
6 myRealm.username = jsmith
7 myRealm.password = secret
8 myRealm.credentialsMatcher = $sha256Matcher
9 securityManager.sessionManager.globalSessionTimeout = 1800000
上例中,第一行与第四行是在定义一个实例,等号前边是实例名称,等号后边是实例的JAVA类。
从第五行到第八行,在定义myRealm的属性,等号前边的内容是包括句号分隔符的。如myRealm.username表示设置的是myRealm这个实现的username属性。第五行到第八行设置的是myRealm的简单属性,可以在等号后边直接把值写进去。第八行这个成员是一个对象,所以可以看到等号后边的$符号,表示这不是普通的简单值,$后边的值sha256Matcher代表实例名称,就是在第二行已经实例化的。
第9行可以看到包含了两个分隔符,因为设置的属性不是SecurityManager的直接成员,是它成员的成员,这个是嵌套属性的配置方法。
上例配置中定义了一个Realm,SecurityManager在实例化后,就从这个Realm作为获取安全数据的源。
假如某属性的值是二进值,很明显在配置之前需要先将数据作转换,有两种方式。一种是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==
...
另一种是用十六进制数表示,但是要注意,转换成16进制后,要在前边加上0x或者x标志,示例如下:
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
在前边的例子中,为实例配置的属性都是单值,如果属性是list、map等集合类属性的话,应该像下边下样配置。
list情况:
1 sessionListener1 = com.company.my.SessionListenerImplementation
...
3 sessionListener2 = com.company.my.other.SessionListenerImplementation
...
5 securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
上例中SecurityManager的直属成员SessionManager,它包括的SessionListeners就是list类型,用来为会话管理实现配置监听器,就像第五行一样,多个值之间用逗号隔开。
map情况:
object1 = com.company.some.Class
object2 = com.company.another.Class
...
anObject = some.class.with.a.Map.property
anObject.mapProperty = key1:$object1, key2:$object2
map的成员也用逗号分隔,每一个成员用分号分隔,前边表示key,后边表示value。
假如不小心出现了如下配置:
...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm
...
myRealm实例的真正类型会是后者,也就是后者会覆盖前者,当然实际上这是一种错误,在配置时要避免。
另上在上边的配置中,securityManager这个实例是直接使用的,如下:
myRealm = ...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
可以看到,securityManager并没有像Realm一样,先定义一个实例如:securityManager=com.xxx.xxx.xxx,而是直接使用。原因就是securityManager一定会被实例化,实例化时也有默认的类,因此不需要先在配置中声明这个实例。当然如果默认的securityManager类无法满足需求,开发者也可以定义自己的实现类,如下:
...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...
一般我们不需要这样做。
在真实的应用中,一般不需要配置[user]部分与接下来的[roles]部分,因为与用户安全相关的敏感数据一般不直接写在配置文件中,而是放置在某种数据源中如数据库,然后在[main]部分为SecurityManager配置Realm从而读取数据。[users]与[roles]这两个部分多数用在测试、演示场景中,将少量并且不需要变更的用户敏感数据直接写在配置文件中。
示例如下:
[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz
[users]下边每一行的格式如下:
username = password, roleName1, roleName2, …, roleNameN
等号前边是用户名,等号后边第一个值是密码,接下来的值全是角色名。
上边的password是明文,password也可以加密,示例如下:
1 [main]
2 ...
3 sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
4 ...
5 iniRealm.credentialsMatcher = $sha256Matcher
6 ...
7
8 [users]
9 # user1 = sha256-hashed-hex-encoded password, role1, role2, ...
10 user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ...
可以看到,第九行是明文,第十行是sha256转换过的,看到这个配置文件的人并不知道user1的真正密码是什么。刚才说了,[users]与[roles]在真实的应用中一般不需要配置,而是在[main]部分为SecurityManager配置安全数据源。但是,如果一旦配置了[users]或者[roles],则相当于自动在[main]部分为SecurityManager增加了一份数据源,它有默认的名称"iniRealm",它的类型是org.apache.shiro.realm.text.IniRealm,效果等同于在[main]部分增加"iniRealm=org.apache.shiro.realm.text.IniRealm"这样一行配置。当然如果配置了[users]或者[roles]这一行是自动添加的,用户不需要添加,可以直接使用。iniRealm会从配置文件的[users]与[roles]部分读取用户数据。
上例中[users]部分的password是经过sha256数字摘要的,因此,iniRealm在收到用户密码时,首先对用户密码作sha256数字摘要,然后再与[users]中配置的内容对比。而第三行与第五行就是告诉iniRealm如何摘要用户密码的。如果没有这个,iniRealm会将收集到的用户password直接与[users]中配置的sha256摘要对比,这个一定是不能通过验证的。
上例中,sha256Matcher是org.apache.shiro.authc.credential.Sha256CredentialsMatcher类型的实例,而org.apache.shiro.authc.credential.Sha256CredentialsMatcher是CredentialsMatcher接口的具体实现。它只是负责对收到的用户password做sha256摘要。而实际上,用户password在数据源保存时的格式可以是各种各样,不只是通过sha256加密,而用户在提交password时也不一定直接就是password原文,也可能是通过password推导出来的其它东西,例如加盐、多种hash值迭代等。开发者可以开发自己的org.apache.shiro.authc.credential.CredentialsMatcher实现,由它来负责如何解析Realm中的用户密码,如何解析用户在认证时提交的password或者各种凭据。当然一般不需要这样做,因为Shiro提供了足够多的CredentialsMatcher实现,例如:
AllowAllCredentialsMatcher
HashedCredentialsMatcher
Md2CredentialsMatcher
Md5CredentialsMatcher
PasswordMatcher
Sha1CredentialsMatcher
Sha256CredentialsMatcher
Sha384CredentialsMatcher
Sha512CredentialsMatcher
SimpleCredentialsMatcher
上边的实现如果还不能满足需求,才需要自己开发,详细参考:http://shiro.apache.org/static/1.3.2/apidocs/org/apache/shiro/authc/credential/CredentialsMatcher.html
这一部分用于定义角色,示例如下:
[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
每一行的格式如下:
rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
关于permissionDefinition,详细参考这里:http://shiro.apache.org/permissions.html,后文也会介绍,这里稍微解释一下上例中的内容。
admin = *
这一行表示admin角色可以作任何事情。
schwartz = lightsaber:*
这一行表示schwartz角色能够对lightsaber做任何事情。这里展开一下,如果只允许schwartz角色对lightsaber执行“看”与“摸”的操作,则配置应该是这个样子:schwartz = "lightsaber:look,touch",等号前的schwartz表示角色名,等号后的lightsaber表示资源名,“:”后的look与touch表示允许的操作。注意新加的双引号,因为本身permissionDefinition中出现了逗号分于分隔不同的操作,所以要用双引号把它括起来,否则的话Shiro会认为lightsaber:look是一条permissionDefinition,而touch是另一条。
goodguy = winnebago:drive:eagle5
看等号后边的值,是用":"分隔的三元组,这是一种更细粒度的控制,winnebago表示资源集合,drive表示能对资源执行什么操作,而eagle5表示资源实例的ID,也就是goodguy角色能够开winnebago资源集合中的eagle5实例。