zoukankan      html  css  js  c++  java
  • Apache Shiro系列之五,概述 —— 配置

    Shiro设计的初衷就是可以运行于任何环境:无论是简单的命令行应用程序还是复杂的企业集群应用。由于运行环境的多样性,所以有多种配置机制可用于配置,本节我们将介绍Shiro内核支持的这几种配置机制。

     
        小贴士:多种配置方案:
        Shiro的SecurityManager是和JavaBean兼容的,所以我们可以使用诸如Java、Xml(Spring、Jboss、Guice等)、YAML、Json、Groovy等配置方式。
     
    一、基于Java代码的配置
        最简单的创建并且使用SecurityManager的方式就是直接在代码中创建org.apache.shiro.mgt.DefaultSecurityManager类实例,比如:
    1 Realm realm =//instantiate or acquire a Realm instance. We'll discuss Realms later.
    2 SecurityManager securityManager =newDefaultSecurityManager(realm);
    3 //Make the SecurityManager instance available to the entire application via static memory:
    4 SecurityUtils.setSecurityManager(securityManager);
    只需区区三行代码,我们就已经为任何类型的应用程序配置好了一个全功能的Shiro运行环境,你看,多简单。
     
        SecurityManager对象图谱
            就像我们在架构一节中介绍的,SecurityManager的实现是模块化的,而且可以兼容JavaBean,所以你可以通过setter和getter方法来配置SecurityManager及其内部组件。
        比如如果你想把一个自定义的SessionDAO配置为SecurityManager的Session管理器,你可以直接调用SessionManager的setSessionDAO方法。
    ...
    DefaultSecurityManager securityManager =newDefaultSecurityManager(realm);
    SessionDAO sessionDAO =newCustomSessionDAO();
    ((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
    ...
    你可以通过这种调用setter方法的方式来设置SecurityManager的任何内置组件。但是对于现实的应用程序来说,这不是一种理想的配置方式。主要有以下几点原因:
        #,这种直接编码的方式要求我们知道这个具体的实现类在哪,并且要自己去创建他。而我们一般建议是依赖于抽象而不是具体,所以最好不要让我知道他具体的实现在哪里。
        #,由于java的类型安全特性,当我们通过getter方法获取到某个类的具体实现之后,我们将不得不把他们强制类型转换为具体的类型,如此多的强制类型转换太丑了,不是一种好的编程实践。
        #,如果我们通过SecurityUtils.setSecurityManager方法为当前的应用设置一个虚拟机范围内的静态SecurityManager对象,在大多数应用中都是ok的。但是如果我们要在一个虚拟机上运行多个使用Shiro的应用程序时,就可能会出乱子了。所以如果能够为每个应用程序创建一个的单例就更好了;
        #,每次你要修改一下Shiro的配置都不得不重新编译程序;
     
        虽然有以上提到的种种缺点,但是如果你要在一个内存受限的环境(比如智能手机)中使用Shiro,使用基于java代码的配置还是不错的选择。而如果内存不太受限的话,使用推荐使用基于文本的配置,因为他对用户更友好,具有更好的可读性。
     
    二、INI配置
        为了让这个文本配置方案能够在所有的开发环境中使用,并且尽可能的减少对于第三方工具的依赖,我们选择了INI格式来配置SecurityManager及其相关组件。INI具有易读、易配置的特性,可以适用于绝大多数的应用。
        
        (一)从INI文件中创建一个SecurityManager
        一下将提供两种基于INI配置文件创建SecurityManager的方法。
        从INI资源文件中创建SecurityManager
        我们可以通过一个INI资源的路径来创建一个SecurityManager,资源可以通过文件系统、classpath、或者url中获取,不同的获取方式需要在资源路径前加不同的前缀,分别是file:, classpath 或者 url:,下面这个例子我们使用一个工厂类从根classpath中找到shiro.ini文件,然后实例化了一个SecurityManager对象。
    1 import org.apache.shiro.SecurityUtils;
    2 import org.apache.shiro.util.Factory;
    3 import org.apache.shiro.mgt.SecurityManager;
    4 import org.apache.shiro.config.IniSecurityManagerFactory;
    5 ...
    6 Factory<SecurityManager> factory =newIniSecurityManagerFactory("classpath:shiro.ini");
    7 SecurityManager securityManager = factory.getInstance();
    8 SecurityUtils.setSecurityManager(securityManager);
        从INI类实例中创建SecurityManager
        如果有需要,我们也可以通过org.apache.shiro.config.Ini类来做INI配置,这个Ini类的API和java.util.Properties类比较像,只是在接口中需要传入Section的名称。
        例如:
     1 import org.apache.shiro.SecurityUtils;
     2 import org.apache.shiro.util.Factory;
     3 import org.apache.shiro.mgt.SecurityManager;
     4 import org.apache.shiro.config.Ini;
     5 import org.apache.shiro.config.IniSecurityManagerFactory;
     6 ...
     7 Ini ini =newIni();
     8 //populate the Ini instance as necessary
     9 ...
    10 Factory<SecurityManager> factory =newIniSecurityManagerFactory(ini);
    11 SecurityManager securityManager = factory.getInstance();
    12 SecurityUtils.setSecurityManager(securityManager);
     
        以上我们已经知道如何通过INI配置来实例化SecurityManager对象,下面就让我们来看看一个真实的Shiro的配置文件到底长什么样。
     
        (二) INI Secions
        所谓的INI文件其实就是按Section分隔的键值对的集合,不同的Section名称不同,每个Section内的键名称要有唯一性。每个Section可以看做就是一个Properties。
        注释行可以井号(#) 开头,也可以 分号(;)开头。、
        下面就是一个Shiro能够解析的INI文件的例子,他的这些Section名称是Shiro所支持的。
    #=======================
    #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
      (1) [main]
    [main]段主要用于配置SecurityManager及其他的依赖项,比如Realms。
    要使用INI这种简单键值对文件格式来配置SecurityManager对象及其依赖项这种层级关系感觉难度有点大,只要稍微加上一些约定,你会发现INI文件能做的远比我们想象的要多,我们把这种基于INI的配置叫做“穷人”的依赖注入。虽然没有Spring/JBoss之类的高富帅那么强大,但是已经足够满足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
        
        定义一个对象
        看下面这段[main]配置片段。
    [main]
    myRealm = com.company.shiro.realm.MyRealm
    ...
        这段配置创建了类 com.company.shiro.realm.MyRealm的一个实例,命名为myRealm。如果这个类实现了org.apache.shiro.util.Nameable接口,则程序会使用参数"myRealm"来调用Nameable.setName接口。
        
        设置对象属性
        基本类型
        基本类型属性可以像下面这样直接赋值。
    ...
    myRealm.connectionTimeout =30000
    myRealm.username = jsmith
    ...
        这段配置翻译成Java代码后是这样的:    
    1 ...
    2 myRealm.setConnectionTimeout(30000);
    3 myRealm.setUsername("jsmith");
    4 ...
        这是如何做到的呢? 这里嘉定所有的对象都是和Java Bean兼容的POJO对象。
        在这种约定的前提下,当给对象设置属性时,Shiro会将所有的脏活、累活都交给Apache Common BeanUtils来干,虽然我们在INI文件内配置的是文本,但是BeanUtils知道如何将一个字符串的值转换为基本类型,并且调用该对象的对应的setter方法来给该POJO设置属性。
        引用类型
        
      如果要设置是引用类型怎么办?你可以用一个美元符号来引用前文中定义的对象,像下面这样,就这么简单。 
    1 ...
    2 sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
    3 ...
    4 myRealm.credentialsMatcher = $sha256Matcher
    5 ...
     
        嵌套属性
        你可以像下面这样引用嵌套属性,给属性赋值,不管有多少个层级,都可以这么用。
    ...
    securityManager.sessionManager.globalSessionTimeout =1800000
    ...
       BeanUtils会把他翻译成下面这样的代码:
    1 securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
        这种嵌套的层级可以要多深有多深,比如:
        object.property1.property2.....propertyN.value=blah
     
        小贴士:BeanUtil支持的属性设置
        只要是BeanUtil支持的属性设置方式你都可以在Shiro配置文件的[main] Section中配置。包括对于set/list/map属性的设置。详情可以参考  Apache Commons BeanUtils Website官方文档。
     
        字节数组
        因为在文本文件中无法直接表示二进制树,所以必须使用一种可以使用文本编码的方式来表示二进制数组,有两种选择,一种是:BASE64,一种是十六进制字符串。默认使用BASE64编码,因为表示相同长度的二进制,BASE64需要的字节更少。这显然更合适文本配置。
        
    #The'cipherKey' attribute is a byte array.Bydefault, text values
    #for all byte array properties are expected to be Base64 encoded:
    securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
    ...
    十六进制的文本当然也是可以的,你要记得在文本的前面加上0x。
    securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
        
        集合属性
        我们可以像设置其他属性一样去设置list、set、map类型的属性,不管是直接的属性还是嵌套属性。对于list和set,我们可以直接使用逗号分隔的值(或者引用)来设置,例如:
    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
    你还可以直接使用对象来作为key,如下:
        anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
        
        一些思考
        顺序问题
        在INI文件中配置的顺序决定了他们翻译成Java代码之后的顺序,这块要小心。
     
        重写实例
        后定义的同名对象会覆盖之前定义的对象,如下第二个myRealm会覆盖掉第一个myRealm的定义,所以最终myRealm是com.company.security.DatabaseRealm的对象实例,而前一个定义的realm将永远不会被引用了。(自然也就被垃圾回收了)。
    ...
    myRealm = com.company.security.MyRealm
    ...
    myRealm = com.company.security.DatabaseRealm
    ...
        默认的SecurityManager
         你可能已经注意到,在上文的配置中,我们并没有创建SecurityManager对象就直接去设置他的嵌套属性了。
    myRealm =...
    securityManager.sessionManager.globalSessionTimeout =1800000
    ...
        SecurityManager是特例,系统已经自动为你创建好了一个SecurityManager对象,你就只管用就好了。当然,如果你说我一定要使用字节实现的一个SecurityManager,那也不是不行,直接像下面这么写就可以了,就像 重写实例 章节中说的,这样就会覆盖掉系统自动创建的SecurityManager对象了。
    ...
    securityManager = com.company.security.shiro.MyCustomSecurityManager
    ...
    当然,这种情况很少发生。因为Shiro默认提供的SecurityManager具有很好的定制性,你几乎可以为配置任何属性。所以如果你发现自己要写一个自定义的SecurityManager的时候,不妨先问问自己:这真的有必要吗?
     
        (2) [users]
        [users]Setion允许你定义一组用户账号。在那些只需要很少的用户账号,或者是那些不需要在运行时动态创建用户账号的运行环境下,这个功能很实用。你可以像下面这么写。
    [users]
    admin = secret
    lonestarr = vespa, goodguy, schwartz
    darkhelmet = ludicrousspeed, badguy, schwartz
     
        小贴士:自动生成的IniRealm
        一旦Shiro发现INI文件的[users], [roles]章节不为空,他就会自动创建一个org.apache.shiro.realm.text.IniRealm类的实例并命名为iniRealm,所以你可以在[main]章节中像配置其他对象一样给iniRealm配置属性。
        行格式
        在[users]的每一行将定义成如下格式
        username=password, roleName1, roleName2, ..., roleNameN
        #,左边的key是用户名;
        #,右边是逗号分隔的密码和角色,其中第一个而是密码,后续的是角色名,角色可以有多个;
        #,角色是可选的;
     
        密码加密
       如果你不想让密码直接用明文显示,也可以使用任何你喜欢的加密算法(MD5,Sha1,Sha256, 等等)来对密码做加密,然后把加密之后的文本复制到INI文件中。加密之后的密码默认应该以十六进制的文本,当然也可以是BASE64的,具体看下问的说明。
        
        小贴士:简单安全的密码
        对密码做加密的最佳实践是使用Shiro提供的  Command Line Hasher工具,他会对密码或者其他你想要加密的文本做哈希,这个工具对于要放在Shiro的INI配置文件的[users]中显示的密码最为实用。
        如果你对密码做了加密,那么你就要告诉Shiro你对密码做了加密,不然他就不认识了。你可以在[main]端中配置Shiro隐式生成的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,...
    和其他属性一样,你可以给CredentialMatcher配置任何值来体现你的哈希策略,比如指定一个盐值,或者是哈希迭代的次数。要想更好的了解哈希策略,你可以查看org.apache.shiro.authc.credential.HashedCredentialsMatcher的API文档。
     
    比如,如果用户的密码是BASE64编码,而不是默认的16进制,则应该像下面这么配置。
    [main]
    ...
    #true= hex,false= base64:
    sha256Matcher.storedCredentialsHexEncoded =false
        
        (3)[roles]
        这个session用于定义角色与权限的对照关系,同样的,这种配置方式对于那些只有少数几种角色,并且不需要在运行时动态创建角色的应用程序特别实用。
    [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
        行格式
        在[roles]中的每一行都必须定义为角色与权限的键值对关系,格式如下:
        rolename=permissionDefinition1,permissionDefinition2,..., permissionDefinitionN
        
        此处permissionDefinition 可以是任意文本,不过一般我们会建议使用和org.apache.shiro.authz.permission.WildcardPermission格式兼容的文本格式,这种格式简单而又灵活。可以查看权限( Permissions)章节来了解更多关于这种权限格式的信息。
        
        小贴士:权限内部的分号
        如果在权限内部要有分号,则在角色定义中必须要用双引号将他们括起来,以避免解析错误,比如printer:5thFloor:print,info,就要写成“printer:5thFloor:print,info
     
        小贴士:不需要权限的角色定义
        如果你的角色是不需要关联权限的,那么你不需要在[roles]章节中来列出他们,你尽管在[users]章节中使用他们就是了。如果系统中不存在这个角色,则Shiro会自动创建对应的角色。
     
        (4)[urls]
        这个Section的描述将放在 Web 章节。
     
    原文地址:http://shiro.apache.org/configuration.html

     本系列相关:

    Apache Shiro系列四概述 ——Shiro的架构



  • 相关阅读:
    .NET 使用EF执行存储过程你知道几种?
    SQLserver 如何优雅的行转列
    SQLserver 如何获取近1月、近3个月、近6月数据
    三汇自动挂断问题:SIP兼容性,ACK检测,忽略ACK开启。
    几个flutter 开源项目测试
    Android versions for all users globally
    Using Flutter 2 on M1 MacOS Apple Silicon
    Educational Codeforces Round 111
    Wannafly挑战赛1
    摆烂记录
  • 原文地址:https://www.cnblogs.com/strinkbug/p/6158522.html
Copyright © 2011-2022 走看看