1 介绍

什么是shiro?

Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证授权企业会话管理加密
shiro能做什么?

  • 验证身份
  • 用户访问权限控制,比如:
    • 判断用户是否分配了一定的安全角色;
    • 判断用户是否被授予完成某个操作的权限;
  • 在非 web 或 EJB 容器的环境下可以任意使用Session API
  • 可以响应认证、访问控制、或者Session生命周期中发生的事件
  • 可以将一个或以上的用户安全数据源数据整合成一个复合的用户“view”(视图)
  • 支持单点登录(SSO)功能
  • 支持提供“Remember Me”服务,获取用户关联信息而无需登录
  • 特性

    在这里插入图片描述
    Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。
  • Authentication(认证):用户身份识别,通常被称为用户“登录”;
  • Authorization(授权):访问控制,比如某个用户是否具有每个操作的使用权限;
  • Session Management(会话管理):特定于用户的会话管理,甚至在非web或EJB应用程序;
  • Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用;
    还提供以下支持:
  • web支持:shiro提供的web支持api,可以很轻松的保护web应用程序的安全;
  • 缓存:缓存是Apache shiro保证安全操作快速、高效的重要手段;
  • 并发:支持多线程应用程序的并发特性;
  • 测试:支持单元测试和集成测试,确保代码和预想的一样安全;
  • Run As:这个功能允许用户采用其他用户的身份(在许可的前提下),有时在管理方案中很有用;
  • Remember Me:跨session记录用户的身份,只有在强制需要时用户才需要登录;

2 教程

运行环境

  • Java1.5及以上
  • Maven2.2.1及以上

    创建shiro-tutorial工程

    建议先去github上创建一个项目,然后在该项目下创建shiro-tutorial模块。

    引入pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gavin.shiro</groupId>
    <artifactId>shiro-tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

            <!-- This plugin is only to test run our little application.  It is not
                 needed in most Shiro-enabled applications: -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <classpathScope>test</classpathScope>
                    <mainClass>Tutorial</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- Shiro uses SLF4J for logging.  We'll use the 'simple' binding
             in this example app.  See http://www.slf4j.org for more info. -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
    </dependencies>
</project>

创建Tutorial.java文件

public class Tutorial {
    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

    public static void main(String[] args) {
        log.info("我的第一个shiro应用");
        System.exit(0);
    }
}

运行测试

在项目根目录下执行mvn compile exec:java,出现下面的打印输出就说明程序运行成功。
在这里插入图片描述

使用shiro

在使用shiro之前要理解一件事情就是:shiro几乎所有事情都和一个中心组件SecurityManager有关(这里的SecurityManager跟java.lang.SecurityManager是两码事)。后面会详细说明shiro的设计,这里只是让读者有个概念,每个程序都必定会存在一个SecurityManager。

配置

通过配置文件的方式来实现对SecurityManager的配置,Shiro默认提供了一个基本的INI配置文件的方案,当然也可以使用XML,YAML,JSON,Groovy Builder markup等多种形式来配置。

shiro.ini

在pom.xml同目录中创建一个src/main/resources子目录,在该子目录下创建一个shiro.ini文件,内容如下:

# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

可以看到,在该配置文件中仅仅配置了几个静态的账户,后面会使用更复杂的用户数据,比如:数据库、LDAP【轻型目录访问协议】和活动目录等。

引用配置

接下来创建SecurityManager实例,用来引用INI文件,这里只需在main函数中进行处理即可。


public class Tutorial {
    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

    public static void main(String[] args) {
        log.info("我的第一个shiro应用");

        /** 1.从指定路径加载配置文件 */
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        /** 2. 分析INI文件,并根据配置文件返回一个SecurityManager实例 */
        SecurityManager securityManager = factory.getInstance();

        /** 3. 将 SecurityManager 设置成了static (memory) singleton,可以通过 JVM 访问 */
        SecurityUtils.setSecurityManager(securityManager);

        System.exit(0);
    }
}

这就是我们要做的–仅仅使用三行代码就把Shiro加进了我们的程序,就是这么简单。

执行mvn compile exec:java 可以看到程序成功的运行(由于 Shiro 默认在 debug 或更底层才记录日志,所以你不会看到任何 Shiro 的日志输出–只要运行时没有错误提示,你就可以知道已经成功了)。

上面所加入的代码做了下面的事情:

使用 Shiro 的 IniSecurityManagerFactory 加载了我们的shiro.ini 文件,该文件存在于 classpath 根目录里。这个执行动作反映出 shiro 支持 Factory Method Design Pattern(工厂模式)。classpath:资源的指示前缀,告诉 shiro 从哪里加载 ini 文件(其它前缀,如 url:和 file: 也被支持)。
2.factory.getInstance() 方法被调用,该方法分析 INI 文件并根据配置文件返回一个 SecurityManager 实例。

3.在这个简单示例中,我们将 SecurityManager 设置成了static (memory) singleton,可以通过 JVM 访问,注意如果你在一个 JVM 中加载多个使用 shiro 的程序时不要这样做,在这个简单示例中,这是可以的,但在其它成熟的应用环境中,通常会将 SecurityManager 放在程序指定的存储中(如在 web 中的 ServletContext 或者 Spring、Guice、 JBoss DI 容器实例)中。

执行安全操作

准备好SecurityManager后,可以开始进行我们真正关心的事情了–执行安全操作。
其实也就是两个问题:“谁是当前的用户?”,“当前用户是否允许做某件事?”,Shiro的API给当前用户定义了一个新的名词,即“Subject”。
在几乎所有的环境中,都可以通过如下语句得到当前用户的信息:

Subject currentUser = SecurityUtils.getSubject();

Subject是一个安全术语,意思是“当前运行用户的指定安全视图”,不仅仅局限于一个人,也可以是第三方进程、时钟守护任务、守护进程账户或者其他,可以简单理解为“当前和软件进行交互的事件”。

在一个独立的程序中调用 getSubject() 会在程序指定位置返回一个基于用户数据的 Subject,在服务器环境(如 web 程序)中,它将获取一个和当前线程或请求相关的基于用户数据的 Subject。

在取得了Subject后,我们可以利用它做点什么呢?

如果你想在应用程序的会话期内给用户提供一些信息,那么我们可以获得他们的session。

Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );

Session是shiro指定的一个实例,提供几乎所有的HttpSession功能,但是同时它又不需要HTTP环境,这句话的意思是在非web程序中,我们也能使用上面这段代码,不必考虑发布环境!从此任何需要 session 的程序不再需要强制使用 HttpSession 或者 EJB Stateful Session,并且,终端可以共享 session 数据。

现在我们获取了一个Subject和它们的Session,可以来搞事情了,比如:检测其是否被允许做某些事情?检查其角色和权限?等等。

我们只能对已知用户进行检查,上面的Subject实例代表当前用户,但是当前用户是谁?如果是匿名用户,那我们就让他们至少登录一次。下面的代码就是完成这个任务:

if ( !currentUser.isAuthenticated() ) {
    //收集用户的主要信息和凭据,来自GUI中的特定的方式
    //如包含用户名/密码的HTML表格,X509证书,OpenID,等。
    //我们将使用用户名/密码的例子因为它是最常见的。
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");

    //支持'remember me' (无需配置,自带的!):
    token.setRememberMe(true);

    currentUser.login(token);
}

如果登录失败呢?那我么就可以捕获所有异常,然后按照自己的方式进行处理:

try {
    currentUser.login( token );
    //无异常,说明就是我们想要的!
} catch ( UnknownAccountException uae ) {
    //username 不存在,给个错误提示?
} catch ( IncorrectCredentialsException ice ) {
    //password 不匹配,再输入?
} catch ( LockedAccountException lae ) {
    //账号锁住了,不能登入。给个提示?
} 
    ... 更多类型异常 ...
} catch ( AuthenticationException ae ) {
    //未考虑到的问题 - 错误?
}

最简单的方式是将异常信息通过人类可以理解的语言返回给用户,不建议直接输出异常原始信息。

除此之外,我们还可以做些什么呢?
显示当前“用户”的信息

//打印主要信息 (本例子是 username):
log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." );

也可以判断它们是否拥有某个角色;

if ( currentUser.hasRole( "schwartz" ) ) {
    log.info("May the Schwartz be with you!" );
} else {
    log.info( "Hello, mere mortal." );
}

还可以判断它们是否拥有某个特定动作或入口的权限;

if ( currentUser.isPermitted( "lightsaber:weild" ) ) {
    log.info("You may use a lightsaber ring.  Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

同样,我们还可以执行非常强大的instance-level(实例级别)的权限检测,检测用户是否具备访问某个类型特定实例的权限:

if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
    log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'.  " +
                "Here are the keys - have fun!");
} else {
    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

最后,当用记不再使用系统,可以退出登录:

currentUser.logout(); //清除识别信息,设置 session 失效.

最终的class

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Author: gavin
 * @GitHub: https://github.com/gavin-yyj
 * @Date: Created in 19:48 2020/6/19
 * @Description:
 */

public class Tutorial {
    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

    public static void main(String[] args) {
        log.info("我的第一个shiro应用");

        //1.从指定路径加载配置文件
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        // 2. 分析INI文件,并根据配置文件返回一个SecurityManager实例
        SecurityManager securityManager = factory.getInstance();

        // 3. 将 SecurityManager 设置成了static (memory) singleton,可以通过 JVM 访问
        SecurityUtils.setSecurityManager(securityManager);

        //获取当前执行的用户
        Subject currentUser = SecurityUtils.getSubject();

        //做点跟Session相关的事
        Session session = currentUser.getSession();
        session.setAttribute("someKey","aValue");
        String value = (String) session.getAttribute("someKey");
        if(value.equals("aValue")){
            log.info("检索出正确的值:"+ value);
        }

        //登录当前用户检验角色和权限
        if(!currentUser.isAuthenticated()){
            //将用户名和密码包装成token
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                //尝试登录
                currentUser.login(token);
            }catch (UnknownAccountException uae){
                log.info("用户名不正确");
            }catch (IncorrectCredentialsException ice){
                log.info("密码不正确");
            }catch (LockedAccountException lae){
                log.info("账号被锁定");
            }
            // ...其他已知异常
            catch (AuthenticationException e){
                log.info("其他未知错误,请联系管理员");
            }
        }

        //说出他们是谁:
        //打印主要识别信息
        log.info("User["+currentUser.getPrincipal()+"] logged in successfully.");

        //测试角色:
        if(currentUser.hasRole("schwartz")){
            log.info("你好!schwartz");
        }else{
            log.info("你好!普通人");
        }

        //测试一个权限(非(instance-level)实例级别)
        if(currentUser.isPermitted("lightsaber:weild")){
            log.info("你可以使用这个光剑戒指,试试吧");
        }else{
            log.info("对不起,光剑戒指仅适用于schwartz大师");
        }

        //一个非常强大的实例级别的权限:
        if(currentUser.isPermitted("winnebago:drive:eagle5")){
            log.info("您可以使用车牌号为eagle5的winnebago,这是钥匙,玩得开心!");
        }else{
            log.info("对不起,您money不够,玩不起");
        }

        //完成,退出
        currentUser.logout();

        System.exit(0);
    }
}

运行结果:
在这里插入图片描述

小结

这节我们通过一个示例介绍了如何在基础程序中加入Shiro,并理解Shiro的设计理念,Subject 和 SecurityManager。
接下来我们将更加深入的理解Shiro的架构及其配置机制。

3 架构

Apache Shiro 设计理念是使程序的安全变得简单直观而易于实现,Shiro的核心设计参照大多数用户对安全的思考模式–如何对某人(或某事)在与程序交互的环境中的进行安全控制。
比如:我们设计一个按钮,如果我的应用程序的交互对象是已经登录认证过的用户,那么我们展示的这个按钮可以用来查看他们的账户信息;如果该用户没有登录,那么这个按钮将作为一个注册按钮。

高级概述

Shio架构包含三个重要的理念:Subject、SecurityManager和Realm,下图展示了这些组件是如何相互作用的,我们将在下面依次对其进行描述:
在这里插入图片描述

  • Subject:Subject本质上是当时运行用户特定的View(视图),而单词“User”经常暗指一个人,Subject可以是一个人,也可以是第三方服务、守护进程账户、时钟守护任务或者其他和当前软件交互的任何事件。Subject实例都和一个SecurityManager绑定,当你和一个Subject进行交互,这些交互动作被转换成SecurityManager下Subject特定的交互动作,比如前面提到的按钮操作。
  • SecurityManager:SecurityManager是Shiro架构的核心,配合内部安全组件共同组成安全伞。然而,一旦一个程序配置好了SecurityManager和它的内部对象,程序开发人员几乎花费所有的时间在Subject API上,但是我们要清楚,任何Subject的安全操作中SecurityManager才是幕后真正的举重者。
  • Realms:Realms是Shiro和你的程序安全数据之间的“桥”或者“连接”,当真正需要和安全性相关的数据(例如用户账户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个程序配置的Realm中查找这些东西。
    Realm本质上是一个特定的安全DAO,它封装与数据源连接的细节,得到Shiro所需的相关数据,在配置Shiro的时候,必须指定至少一个Realm来实现认证(Authentication)和/或授权(Authorization)。SecurityManager可以配置多个复杂的Realm,但是至少保证有一个。
    Shiro提供开箱即用的Realms来连接安全数据源(或叫地址)如:LDAP、JDBC、文件配置如INI和属性文件等,如果已有的Realm不能满足你的需求,你也可以开发自己的Realm实现,和其他内部组件一样,Shiro SecurityManager管理如何使用Realms获取Subject实例所代表的安全和身份信息。

详细架构

在这里插入图片描述

  • Subject:正在与软件交互的一个特定的实体“view”(用户、第三方服务、时钟守护任务等)

  • SecurityManager:shiro的核心,用来协调它管理的组件使之平稳地一起工作,同时它也管理着Shiro中每一个程序用户的视图,所以它知道每个用户如何执行安全操作。

  • Authenticator:Authenticator是负责执行用户的身份验证(登录)并对其作出反应的组件。 当用户尝试登录时,该逻辑由身份验证器执行。 身份验证器知道如何与一个或多个存储相关用户/帐户信息的领域进行协调。 从这些领域获得的数据用于验证用户的身份,以确保用户确实是他们所说的真实身份。

  • Authentication Strategy:如果配置了多个Realm,AuthenticationStrategy将会协调Realm,以确定在一个身份验证成功或失败的条件。(比如:如果在一个方面验证成功了,但是其他方面有验证失败了,那么这次尝试是否成功,由Authentication Strategy说了算)

  • Authorizer:Authorizer是负责程序中用户访问控制的组件,它是最终判断一个用户是否允许做某件事的途径,像Authenticator一样,Authorizer也知道如何通过协调多种后台数据源来访问角色和权限信息,Authorizer利用这些信息来准确判断一个用户是否可以执行给定的动作。

  • SessionManager:SessionManager知道如何创建并管理用户Session生命周期,从而在所有环境中为用户提供一个强有力的Session体验。Shiro 将使用现有的session(如Servlet Container),但如果环境中没有,比如在一个独立的程序或非 web 环境中,它将使用它自己建立的 session 提供相同的作用,sessionDAO 用来使用任何数据源使 session 持久化。

  • SessionDAO:SessionDAO用于将session持久化到数据库,实现对其增删改查。

  • CacheManager:CacheManager为Shiro的其他组件提供创建缓存实例和管理缓存生命周期的功能,利用缓存来提高访问效率,目前主流开源或企业级缓存框架都可以集成到Shiro中。

  • Cryptography:Shiro的crypto包包含了易用且易懂的加密方式,Hashes(即digests)和不同的编码实现。

  • Realms:Realm 是 shiro 和你的应用程序安全数据之间的“桥”或“连接”,当实际要与安全相关的数据进行交互如用户执行身份认证(登录)和授权验证(访问控制)时,shiro 从程序配置的一个或多个Realm 中查找这些数据,你需要配置多少个 Realm 便可配置多少个 Realm(通常一个数据源一个),shiro 将会在认证和授权中协调它们。

    SecurityManager

    因为 Shiro API 鼓励以 Subject 为中心的开发方式,大部分开发人员将很少会和 SecurityManager 直接交互(尽管框架开发人员也许发现它非常有用),尽管如此,知道 SecurityManager 如何工作,特别是当在一个程序中进行配置的时候,是非常重要的。

    设计

    程序中SecurityManager执行操作并且管理所有程序用户的状态,在Shiro基础的SecurityManager实现中,包含以下内容:

  • 认证(Authentication)

  • 授权(Authorization)

  • 会话管理(Session Management)

  • 缓存管理(Cache Management)

  • Realm协调(Realm coordination)

  • 事件传导(Event propagation )

  • “RememberMe” 服务(”Remember Me” Services)

  • 建立Subject(Subject creation)

  • 退出登录(Logout)

以上这些功能都放在一个单独的组件中进行管理,为了实现配置的简单、灵活、机动性,Shiro的实现在设计上都是高度模块化的,SecurityManager类及其实现类主要充当轻量级的“容器”组件功能,几乎将所有的行为委托给嵌套/包装的组件,这里可以参考上面的架构图。

当组件执行逻辑的时候,SecurityManager知道如何以及何时去协调组件做出正确的动作。
SecurityManager 和 JavaBean 兼容,这允许你(或者配置途径)通过标准的JavaBean 访问/设置方法(get/set)很容易地定制插件,这意味着 Shiro 模块可以根据用户行为转化成简易的配置。

4 配置

Shiro 的 SecurityManager 的实现和其所依赖的组件都是 JavaBean,所以可以用多种形式对 Shiro 进行配置,比如XML(Spring, JBoss, Guice, 等等),YAML, JSON, Groovy Builder markup等,INI 只是 Shiro 一种最基本的配置方式,使得其可以在任何环境中进行配置。

在程序中配置

伪代码如下:

Realm realm = //实例化或获得一个Realm的实例。我们将稍后讨论Realm。

SecurityManager securityManager = new DefaultSecurityManager(realm);

//使SecurityManager实例通过静态存储器对整个应用程序可见:
SecurityUtils.setSecurityManager(securityManager);

如同我们在架构(Architecture )中讨论过的,Shiro SecurityMangger 本质上是一个由一套安全组件组成的对象模块视图(graph),因为与 JavaBean兼容,所以可以对所有这些组件调用的 getter 和 setter 方法来配置SecurityManager 和它的内部对象视图。

例如,你想用一个自定义的 SessionDAO 来定制 Session Management从而配置一个 SecurityManager 实例,你就可以使用 SessionManager 的 setSessionDAO 方法直接 set 这个 SessionDAO。

DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);

SessionDAO sessionDAO = new CustomSessionDAO();

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

使用这些函数,你可以配置 SecurityManager 视图(graph)中的任何一部分。

虽然在程序中配置很简单,但它并不是我们现实中配置的完美解决方案。在几种情况下这种方法可能并不适合你的程序:

  • 它需要你确切知道并实例化一个直接实现(direct implementation),然而更好的做法是你并不需要知道这些实现也不需要知道从哪里找到它们。

  • 因为JAVA类型安全的特性,你必须对通过 get* 获取的对象进行强制类型转换,这么多强制转换非常的丑陋、累赘并且会和你的类紧耦合。

  • SecurityUtils.setSecurityManager 方法会将 SecurityManager实例化为虚拟机的单独静态实例,在大多数程序中没有问题,但如果有多个使用 Shiro 的程序在同一个 JVM 中运行时,各程序有自己独立的实例会更好些,而不是共同引用一块静态内存。

  • 改变配置就需要重新编译你的程序。

然而,尽管有这些不足,在程序中定制的这种方法在限制内存(memory-constrained )的环境中还是很有价值的,像智能电话程序。如果你的程序不是运行在一个限制内存的环境中,你会发现基于文本的配置会更易读易用。

INI 配置

大多数程序已经改为使用基于文本的配置,不需要依靠代码就可进行修改;
为了确保具有共性的基于文本配置的途径适用于任何环境而且减少对第三方的依赖,Shiro 支持使用 INI 创建 SecurityManager 对象视图(graph)以及它支持的组件,INI 易读易配置,很容易创建并且对大多数程序都很适合。

通过INI资源创建 SecurityManager

这里举两个通过INI配置创建SecurityManager的例子。

从INI资源创建SecurityManager

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

通过INI实例创建SecurityManager

INI 配置可以通过org.apache.shiro.config.Ini类用程序方式创建,这个 INI 类类似于 JDK 的java.util.Properties类,但支持通过section 名分割。如:

Ini ini = new Ini();
//populate the Ini instance as necessary
...
Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

接下来解决如何定义一个Shiro INI配置文件的问题

INI Sections

INI 基于文本配置,在独立命名的区域内通过成对的键名/键值组成。键名在每个区域内必须唯一,但是在整个配置文件中并不要求唯一,每个区域(session)可以看作是一个独立的Properties定义。
注释行可以用“#”或“;”标识,下面是一个关于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

[main]

[main]区域是配置程序 SecurityManager 实例及其支撑组件的地方,如 Realm。

[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

1、定义一个对象
这一行实例化了一个类型为 com.company.shiro.realm.MyRealm的对象实例并且使对象使用 myRealm 作为名称以便于将来引用和配置。

如果对象实例化时实现了 org.apache.shiro.util.Nameable接口,Nameable.setName方法将被以该名(在此例中为myRealm)命名的对象调用。

2、设置对象属性
上面代码等价于

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

这是因为Shiro默认使用Apache通用的BeanUtils来完成这项复杂的工作,BeanUtils知道如何将这些字符串值转换为适合的原始值类型并调用合适的JavaBeans的setter方法。

3、引用值
如果你想设置的值并不是一个原始值,而是另一个对象怎么办呢?你可以使用一个 $ 符来引用一个之前定义的实例。

4、嵌套属性
通过在等号左侧使用点符号,你可以得到你希望设置对象视图最终的对象/属性;
securityManager.sessionManager.globalSessionTimeout = 1800000等价于securityManager.getSessionManager().setGlobalSessionTimeout(1800000);

5、字节数组值
因为原始的字节数组不能直接在文本中定义,我们必须使用字节数组的文本编码。可以使用64位编码(默认)或者16位编码,使用16位编码必须在字符串前面加上0x前缀;
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008

6、集合属性
列表(Lists)、集合(Sets)、图(Maps)可以像其它属性一样设置–直接设置或者像嵌套属性一样,对于列表和集合,只需指定一个逗号分割的值集或者对象引用集。

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

注意事项

1、顺序问题
每一个对象实例以及每一个指定的值都将按照其在 [main] 区域中产生的顺序的执行,这些行最终转换为 JavaBeans 的 getter/setter 方法调用,这些方法按同样的顺序调用。
2、覆盖实例
每个对象都可以被后定义的新实例覆盖:

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

这样的结果是 myRealm 是com.company.security.DatabaseRealm 实例而前面的实例不会被使用(会作为垃圾回收)。
3、默认Default SecurityManager
securityManager实例是特殊的–它已经为你实例化过了并且准备好了,所以你并不需要知道指定的实例化SecurityManager的实现类。

当然,如果你确实想指定你自己的实现类,你可以像上面的覆盖实例那样定义你自己的实现:

securityManager = com.company.security.shiro.MyCustomSecurityManager

当然,很少需要这样–Shiro 的 SecurityManager 实现可以按需求进行定制,你可能要问一下自己(或者用户群)你是否真的需要这样做。

[users]

[users]区域允许你定义一组静态的用户帐号,这对于那些只有少数用户帐号并且用户帐号不需要在运行时动态创建的环境来说非常有用。下面是一个例子:

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

定义非空的[users]或[roles]区域将自动创建org.apache.shiro.realm.text.IniRealm 实例,在[main]区域下生成一个可用的 iniRealm ,你可以像上面配置其它对象那样配置它。

格式:

username = password, roleName1, roleName2, ..., roleNameN
  • 等号左边的值是用户名;
  • 等号右侧第一个值是用户密码,密码是必须的;
  • 密码之后用逗号分割的值是赋予用户的角色名,角色名是可选的。

密码加密
如果你不希望[users]区域下的密码以明文显示,你可以用你喜欢的哈希算法(MD5, Sha1, Sha256, 等)来加密它们,将加密后的字符串作为密码值,默认的情况下密码用16位编码算法,但也可以用64位编码算法替代(如下)

指定哈希文本密码值后,您必须告诉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, ...

如果用64位编码方式进行编码,需要指定:

[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

格式
[roles]区域下的每一行必须用下面的格式定义角色-权限的键/值对应关系。
rolename = permissionDefinition1, permissionDefinition2, ..., permissionDefinitionN

注意如果一个特定的权限定义需要用到逗号分隔(如:printer:5thFloor:print,info),你需要将该定义用双引号括起来从而避免出错:”printer:5thFloor:print,info”。

如果你有不需要权限的角色,不需要将它们列入[roles]区域,仅仅在 [users]区域定义角色名就可以创建它们。

[urls]

Web章节讨论


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

shiro系列-2.核心 Previous
shiro系列-shiro入门示例 Next