一、在web环境下使用Shiro使用
将 Shiro 集成到任何 Web 应用程序的最简单的方法是在 web.xml 中配置 ContextListener 和 Filter,理解如何读取 Shiro 的 INI 配置文件。
1、web.xml配置
<!-- Shiro --> <listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
这假设一个 Shiro INI Configuration 文件在以下两个位置任意一个,并使用最先发现的那个:
/WEB-INF/shiro.ini
在classpath 根目录下shiro.ini 文件
下面是上述配置所做的事情:
EnvironmentLoaderListener 初始化一个Shiro WebEnvironment 实例(其中包含 Shiro 需要的一切操作,包括 SecurityManager ),使得它在 ServletContext 中能够被访问。如果你需要在任何时候获得WebEnvironment 实例,你可以调用WebUtils.getRequiredWebEnvironment(ServletContext)。
ShiroFilter 将使用此 WebEnvironment 对任何过滤的请求执行所有必要的安全操作。
最后,filter-mapping 的定义确保了所有的请求被 ShiroFilter 过滤,建议大多数 Web 应用程序使用以确保任何请求是安全的。
ShiroFilter filter-mapping
它通常可取的做法是在任何其他 filter-mapping 声明之前定义 ShiroFilter filter-mapping,以确保 Shiro 也能在那些过滤器 下工作的很好。
IniWebEnvironment 将会去读取和加载 INI 配置文件。默认情况下,这个类会自动地在下面两个位置寻找 Shiro.ini 配置(按顺序)。
/WEB-INF/shiro.ini
classpath:shiro.ini
它将使用最先发现的那个。 然而,如果你想把你的配置放在另一位置,你可以在 web.xml 中用contex-param 指定该位置。
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>YOUR_RESOURCE_LOCATION_HERE</param-value>
</context-param>
默认情况下,在 ServletContext.getResource 方法定义的规则下,param-value 是可以被解析的。例如, /WEB-INF/some/path/shiro.ini。
三、INI Configuration配置
为了确保具有共性的基于文本配置的途径适用于任何环境而且减少对第三方的依赖,Shiro 支持使用 INI 创建 SecurityManager 对象视图(graph)以及它支持的组件,INI 易读易配置,很容易创建并且对大多数程序都很适合。
INI 基于文本配置,在独立命名的区域内通过成对的键名/键值组成。键名在每个区域内必须唯一,但在整个配置文件中并不需要这样(这点和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]区域是配置程序 SecurityManager 实例及其支撑组件的地方,如 Realm。
通过INI配置像 SecurityManager 的对象实例及其支撑组件听起来是一件很困难的事情,因为在这里我们只能用键名/键值对。但通过定义一些对象视图(graphs)可以理解的惯例,你发现你完全可以这样做。Shiro 利用这些假定的惯例来实现一个简单而简明的配置途径。
我们经常将这种方法认为是“可怜人的(poor man's)”的依赖注入,虽然不及成熟的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]中包含以下片段。
[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 通用的BeanUtils 来完成这项复杂的工作,所以虽然 INI 值是文本,BeanUtils 知道如何将这些字符串值转换为适合的原始值类型并调用合适的 JavaBeans 的 setter 方法。
Reference Values 引用值
如果你想设置的值并不是一个原始值,而是另一个对象怎么办呢?你可以使用一个 $ 符来引用一个之前定义的实例,如:
... sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher ... myRealm.credentialsMatcher = $sha256Matcher ...
这定义了名为 sha256Matcher 的对象并且使用 BeanUtils 将其设置到myRealm 的实例中(通过调用 myRealm.setCredentialsMatcher(sha256Matcher) 方法)。
Nested Properties 嵌套属性
通过在等号左侧使用点符号,你可以得到你希望设置对象视图最终的对象/属性,例如下面这行配置:
... securityManager.sessionManager.globalSessionTimeout = 1800000 ...
转换逻辑为(通过BeanUtils):
securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
用这种方法访问的层数需要多深可以有多深: object.property1.property2....propertyN.value = blah
BeanUtils 属性支持
BeanUtils 支持任何指定的属性操作,在 Shiro [main] 区域中setProperty方法将被调用,包括集合(set)/列表(list)/图(map),查看Apache Commons BeanUtils Website和文档了解更多的信息。
Byte Array Values 字节数组值
因为原始的字节数组不能直接在文本中定义,我们必须使用字节数组的文本编码。可以使用64位编码(默认)或者16位编码,默认为64位编码因为使用64位编码实际文字会少一些--它拥有很大的编码表,这意味着你的标识会更短(对于文本配置来讲会好一些)。
# 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 前缀:
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
Collection Properties 集合属性
列表(Lists)、集合(Sets)、图(Maps)可以像其它属性一样设置--直接设置或者像嵌套属性一样,对于列表和集合,只需指定一个逗号分割的值集或者对象引用集。
如定义一些SessionListeners:
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
在上面的例子中,$object1 引用的对象将存于键 key1 之下,也就是map.get("key1") 将返回 object1。你也可以使用其它对象作为键值:
anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2 ...
Considerations 注意事项
Order Matters 顺序问题
上述 INI 格式和约定非常方便也非常易懂,但它并没有另外一种 text/XML的配置路径强大,通过上述途径进行配置需要知道非常重要的一件事情就是顺序问题!
小心
每一个对象实例以及每一个指定的值都将按照其在 [main] 区域中产生的顺序的执行,这些行最终转换为 JavaBeans 的 getter/setter 方法调用,这些方法按同样的顺序调用。
当你写配置文件的时候要牢记于此。
Overriding Instances 覆盖实例
每一个对象都可以被后定义的新实例覆盖,例如,第二个myRealm定义将重写第一个:
... myRealm = com.company.security.MyRealm ... myRealm = com.company.security.DatabaseRealm ...
这样的结果是 myRealm 是 com.company.security.DatabaseRealm 实例而前面的实例不会被使用(会作为垃圾回收)。
Default SecurityManager 默认Default SecurityManager
你可能注意到在以上所有例子中都没有定义 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 实例,在[main]区域下生成一个可用的 iniRealm ,你可以像上面配置其它对象那样配置它。
Line Format 格式
[users]区域下每一行必须和下面的形式一致:
username = password, roleName1, roleName2, ..., roleNameN
等号左边的值是用户名;
等号右侧第一个值是用户密码,密码是必须的;
密码之后用逗号分割的值是赋予用户的角色名,角色名是可选的。
Encrypting Passwords 密码加密
如果你不希望[users]区域下的密码以明文显示,你可以用你喜欢的哈希算法(MD5, Sha1, Sha256, 等)来加密它们,将加密后的字符串作为密码值,默认的,密码建议用16位编码算法,但也可以用64位编码算法替代(如下)
简单的安全密码
为了节约时间获得最佳实践,你可以使用 Shiro 的 Command Line Hasher,它可以加密密码和其它类型的资源,尤其使给 INI[user] 密码加密变得非常简单。
一旦你指定了加密后的密码值,你必须告诉 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 的所有属性,例如,指定使用salting或者有多少hash iterations执行,可以查看org.apache.shiro.authc.credential.HashedCredentialsMatcher Java文档更好地理解 hashing 策略,可能会很有帮助。
例如,如果你用64位编码方式取代了16位编码方式,你应该指定:
[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
Line Format 格式
[roles]区域下的每一行必须用下面的格式定义角色-权限的键/值对应关系。
rolename = permissionDefinition1, permissionDefinition2, ..., permissionDefinitionN
权限定义可以是非常随意的字符串,但大部分用户还是希望使用易用而灵活的和org.apache.shiro.authz.permission.WildcardPermission形式一致的字符串格式。查看 Permissions 文档获取更多关于权限的信息和你可以如何利用它为你服务。
内部用法
注意如果一个特定的权限定义需要用到逗号分隔(如:printer:5thFloor:print,info),你需要将该定义用双引号括起来从而避免出错:"printer:5thFloor:print,info"。
没有权限的角色
如果你有不需要权限的角色,不需要将它们列入[roles]区域,仅仅在 [users]区域定义角色名就可以创建它们(如果它们尚不存在)。
[urls]
在urls 项的每一行格式如下:
URL_Ant_Path_Expression = Path_Specific_Filter_Chain