zoukankan      html  css  js  c++  java
  • Apache Shiro系列教程之二:十分钟上手Shiro

    在本教程中,我们会写一个简单的、仅仅输出一些内容命令行程序,从而对Shiro有一个大体的感觉。

    一、准备工作

    本教程需要Java1.5+,并且我们用Maven生成项目,当然Maven不是必须的,你也可以通过导入Shiro jar包的方式、或使用Ant、Ivy,喜欢哪种就用哪种。

    开始之前,确定你的Maven版本为2.2.1+(如果你用的是Maven的话),用mvn --version确定Maven的版本。

    现在,我们将正式开始。首先新建一个文件夹,比如说shiro-tutorial,然后将下面的Maven pom.xml文件放到该文件夹下。

    pom.xml

     1 <?xml version="1.0" encoding="UTF-8"?>
     2     <project xmlns="http://maven.apache.org/POM/4.0.0"
     3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     5 
     6     <modelVersion>4.0.0</modelVersion>
     7     <groupId>org.apache.shiro.tutorials</groupId>
     8     <artifactId>shiro-tutorial</artifactId>
     9     <version>1.0.0-SNAPSHOT</version>
    10     <name>First Apache Shiro Application</name>
    11     <packaging>jar</packaging>
    12 
    13     <properties>
    14         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    15     </properties>
    16 
    17     <build>
    18         <plugins>
    19             <plugin>
    20                 <groupId>org.apache.maven.plugins</groupId>
    21                 <artifactId>maven-compiler-plugin</artifactId>
    22                 <version>2.0.2</version>
    23                 <configuration>
    24                     <source>1.5</source>
    25                     <target>1.5</target>
    26                     <encoding>${project.build.sourceEncoding}</encoding>
    27                 </configuration>
    28             </plugin>
    29 
    30         <!-- This plugin is only to test run our little application.  It is not
    31              needed in most Shiro-enabled applications: -->
    32             <plugin>
    33                 <groupId>org.codehaus.mojo</groupId>
    34                 <artifactId>exec-maven-plugin</artifactId>
    35                 <version>1.1</version>
    36                 <executions>
    37                     <execution>
    38                         <goals>
    39                             <goal>java</goal>
    40                         </goals>
    41                     </execution>
    42                 </executions>
    43                 <configuration>
    44                     <classpathScope>test</classpathScope>
    45                     <mainClass>Tutorial</mainClass>
    46                 </configuration>
    47             </plugin>
    48         </plugins>
    49     </build>
    50 
    51     <dependencies>
    52         <dependency>
    53             <groupId>org.apache.shiro</groupId>
    54             <artifactId>shiro-core</artifactId>
    55             <version>1.1.0</version>
    56         </dependency>
    57         <!-- Shiro uses SLF4J for logging.  We'll use the 'simple' binding
    58              in this example app.  See http://www.slf4j.org for more info. -->
    59         <dependency>
    60             <groupId>org.slf4j</groupId>
    61             <artifactId>slf4j-simple</artifactId>
    62             <version>1.6.1</version>
    63             <scope>test</scope>
    64         </dependency>
    65     </dependencies>
    66 
    67 </project>

    二、The Tutorial class

    由于我们的目的是创建一个命令行程序,因此我们需要先新建一个具有public static void main(String[] args)的Java类。

    在和pom.xml所处的同一目录下,创建src/main/java子文件夹。在src/main/java文件夹下创建Tutorial.java文件,文件内容如下:

    src/main/java/Tutorial.java

     1 import org.apache.shiro.SecurityUtils;
     2 import org.apache.shiro.authc.*;
     3 import org.apache.shiro.config.IniSecurityManagerFactory;
     4 import org.apache.shiro.mgt.SecurityManager;
     5 import org.apache.shiro.session.Session;
     6 import org.apache.shiro.subject.Subject;
     7 import org.apache.shiro.util.Factory;
     8 import org.slf4j.Logger;
     9 import org.slf4j.LoggerFactory;
    10 
    11 public class Tutorial {
    12 
    13     private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
    14 
    15     public static void main(String[] args) {
    16         log.info("My First Apache Shiro Application");
    17         System.exit(0);
    18     }
    19 }

    在往下进行之前,先测试一下是否可以运行。

    三、测试运行

    首先进入项目根目录(本教程为shiro-tutorial目录,即pom.xml所在的目录),打开控制台,输入命令:

    mvn compile exec:java
    

    然后你就会看到这个小程序运行起来,并且有如下类似的输出:

    Run the Application
    lhazlewood:~/projects/shiro-tutorial$ mvn compile exec:java
    
    ... a bunch of Maven output ...
    
    1 [Tutorial.main()] INFO Tutorial - My First Apache Shiro Application
    lhazlewood:~/projects/shiro-tutorial$

    现在我们已经验证了程序可以运行,接下来让我们使用Shiro。每次改变程序后,都可以运行mvn compile exec:java运行程序。

    四、使用Shiro

    在Shiro中有一个非常重要的组件--SecurityManager,Shiro的所有功能几乎都与这个组件相关。这样Java security类似,但是和java.lang.SecurityManager是不一样的。

    我们会在后续教程中对SecurityManager做详细介绍。但是现在我们就要明确一点:SecurityManager是Shrio的核心组件,并且任何一个应用都要有一个SecurityManager才可以使用Shiro的其他功能。所有,在本教程中必须要做的事就是实例化一个SecurityManager。

    五、配置

    即使我们可以直接实例化一个SecurityManager,然而SecurityManager还是有比较多的配置和内部组件的,直接用java代码配置这些内容比较麻烦。通过配置文件进行配置会比较简单。

    Shiro提供了一个默认的‘common denominator’,这是一个简单的文本配置文件(INI格式)。与XML格式相比,INI格式更加易读、易用并且几乎不需要任何依赖。INI格式可以轻松的配置SecurityManager。

    事实上,由于Shiro完全兼容了JavaBeans,所以Shiro可以用XML、YAML、JSON、Groovy等很多格式进行配置。

    下面我们用INI文件配置本教程的SecurityManager。首先,在pom.xml所在文件夹下创建src/main/resources文件夹,然后在src/main/resources文件夹下新建一个名为shiro.ini的文件,该文件内容如下:

    src/main/resources/shiro.ini

     1 # =============================================================================
     2 # Tutorial INI configuration
     3 #
     4 # Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
     5 # =============================================================================
     6 
     7 # -----------------------------------------------------------------------------
     8 # Users and their (optional) assigned roles
     9 # username = password, role1, role2, ..., roleN
    10 # -----------------------------------------------------------------------------
    11 [users]
    12 root = secret, admin
    13 guest = guest, guest
    14 presidentskroob = 12345, president
    15 darkhelmet = ludicrousspeed, darklord, schwartz
    16 lonestarr = vespa, goodguy, schwartz
    17 
    18 # -----------------------------------------------------------------------------
    19 # Roles with assigned permissions
    20 # roleName = perm1, perm2, ..., permN
    21 # -----------------------------------------------------------------------------
    22 [roles]
    23 admin = *
    24 schwartz = lightsaber:*
    25 goodguy = winnebago:drive:eagle5

    如你所见,这个文件设置了一些基本的用户帐户,这对本教程已经足够了。在后续章节中,你将会学习如何使用更加复杂的用户数据源,如数据库、LDAP、ActiveDirectory等。

    六、引用配置文件

    现在我们已经有了一个INI文件,这样我们就可以创建一个SecurityManager对象了。将Tutorial.java文件中的main函数做如下改变:

    public static void main(String[] args) {
    
        log.info("My First Apache Shiro Application");
    
        //1.
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    
        //2.
        SecurityManager securityManager = factory.getInstance();
    
        //3.
        SecurityUtils.setSecurityManager(securityManager);
    
        System.exit(0);
    }

    现在,我们只用了三行代码就将Shiro引入到我们的项目中了。可以用mvn compile exec:java检测一下程序是否可以运行。

    在上述代码中,我们做了三件事:

    1. 我们使用了Shiro的IniSecurityManagerFactory类读取shiro.ini文件。从这个类名我们可以看出Shiro使用率设计模式中的工厂模式。代码中的classpath前缀告诉shiro去哪里加载ini文件(支持的前缀还有url:,file:)
    2. factory.getInstance()函数解析ini文件并返回一个Securitymanager对象,该对象含有配置信息。
    3. 在本例中,我们将SecurityManager设置为一个静态的,实现了单例模式,可以通过java虚拟机获得。然而如果要在多个应用中使用shiro,这样做就不行了。从而在比较复杂的大型应用中,我们通常将SecurityManager放在一块应用内存中,如web应用中的ServletContec或Spring、Guice或JBoss DI容器。

    七、使用Shiro

    现在SecurityManager已经设置好了,我们终于可以做一些真正地与安全相关的操作了、

    当我们考虑应用的安全性时,通常会遇到的问题是“当前用户是谁?”,“当前用户可以做什么?”所以,应用的安全性工作主要建立在当前用户之上。在shiro API中用Subject这个含义更广的概念代替当前用户这个概念。

    几乎在任何环境中,你都可以通过下述代码获得当前正在执行程序的用户。

    Subject currentUser = SecurityUtils.getSubject();

    使用SecurityUtils.getSubject方法,我们可以获得当前正在执行程序的Subject。我们并不称之为前正在执行程序的用户,因为用户通常是指人,而Subject可以指人、进程、计划任务、守护进程等。准确的说,Subject指的是“当前和软件交互的事物”。在多数场景中,你可以将Subject粗暴地认为是用户。

    在一个独立的程序中,getSubject()函数基于应用内存中的用户数据返回一个Subject,在一个服务器环境中(如web应用),Subject通常是基于与当前进程有关的用户数据或是来到的请求。

    既然我们已经有了Subject,我们可以拿它来做什么?

    你可以获取当前Session并存储一些东西。

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

    这里的Session是基于Shiro的,它的功能与HttpSession类似,但是有一个巨大的不同:它不需要HTTP环境!

    如果在web应用中部署shiro,则Session默认就是基于HttpSession的。但是在非web应用中,比如本教程,Shiro会自动用它的Enterprise Session Managerment。这样你就可以使用一样的API而不用管部署环境是什么了。

    现在我们已经获得了Subject和它的Session,那么怎么用这些东西去检测Subject是否具有某权限、某许可呢?

    我们只能对当前用户检测这些东西。我们的Subject对象就是当前用户,但是Subject是谁?不知道,它是匿名的,除非它至少登录过一次,否则我们无从得知Subject是谁。所以,我们让Subject登录:

     1 if ( !currentUser.isAuthenticated() ) {
     2     //collect user principals and credentials in a gui specific manner
     3     //such as username/password html form, X509 certificate, OpenID, etc.
     4     //We'll use the username/password example here since it is the most common.
     5     UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
     6 
     7     //this is all you have to do to support 'remember me' (no config - built in!):
     8     token.setRememberMe(true);
     9 
    10     currentUser.login(token);
    11 }

    现在,Subject已经登录了。

    如果登录失败,我们可以捕获异常并且做相应处理。

     1 try {
     2     currentUser.login( token );
     3     //if no exception, that's it, we're done!
     4 } catch ( UnknownAccountException uae ) {
     5     //username wasn't in the system, show them an error message?
     6 } catch ( IncorrectCredentialsException ice ) {
     7     //password didn't match, try again?
     8 } catch ( LockedAccountException lae ) {
     9     //account for that username is locked - can't login.  Show them a message?
    10 }
    11     ... more types exceptions to check if you want ...
    12 } catch ( AuthenticationException ae ) {
    13     //unexpected condition - error?
    14 }

    这里有很多不同类型的异常,你也可以自定义自己的异常。

    到这一步,我们已经有了一个登录过的用户,我们可以来做些什么呢?

    我们来看看它是谁:

    //print their identifying principal (in this case, a 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.");
    }

    我们也可以进行一些insstance-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(); //removes all identifying information and invalidates their session too.

    八、Tutorial Class文件的最终内容

    Final src/main/java/Tutorial.java

    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.apache.shiro.util.Factory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Tutorial {
    
        private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
    
        public static void main(String[] args) {
            log.info("My First Apache Shiro Application");
    
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
    
            // get the currently executing user:
            Subject currentUser = SecurityUtils.getSubject();
    
            // Do some stuff with a Session (no need for a web or EJB container!!!)
            Session session = currentUser.getSession();
            session.setAttribute("someKey", "aValue");
            String value = (String) session.getAttribute("someKey");
            if (value.equals("aValue")) {
                log.info("Retrieved the correct value! [" + value + "]");
            }
    
            // let's login the current user so we can check against roles and permissions:
            if (!currentUser.isAuthenticated()) {
                UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
                token.setRememberMe(true);
                try {
                    currentUser.login(token);
                } catch (UnknownAccountException uae) {
                    log.info("There is no user with username of " + token.getPrincipal());
                } catch (IncorrectCredentialsException ice) {
                    log.info("Password for account " + token.getPrincipal() + " was incorrect!");
                } catch (LockedAccountException lae) {
                    log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                            "Please contact your administrator to unlock it.");
                }
                // ... catch more exceptions here (maybe custom ones specific to your application?
                catch (AuthenticationException ae) {
                    //unexpected condition?  error?
                }
            }
    
            //say who they are:
            //print their identifying principal (in this case, a username):
            log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
    
            //test a role:
            if (currentUser.hasRole("schwartz")) {
                log.info("May the Schwartz be with you!");
            } else {
                log.info("Hello, mere mortal.");
            }
    
            //test a typed permission (not instance-level)
            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.");
            }
    
            //a (very powerful) Instance Level permission:
            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!");
            }
    
            //all done - log out!
            currentUser.logout();
    
            System.exit(0);
        }
    }

    九、总结

    希望通过本教程,你可以知道如何设置shiro,并且了解Subject和SecurityManager这两个基本概念。

    但是这只是一个非常非常简单的应用。你可能会问“如果我不想要INI而想用更加复杂的数据源该怎么做?”

    为了解答这个问题,我们需要更加深入地了解一下shiro的结构,我门将在后面的章节中学习。

  • 相关阅读:
    【L.M.W.Y.D】Scrum Meeting 5
    【L.M.W.Y.D】Scrum Meeting 4
    多喝热水 实验十 团队作业6:团队项目用户验收&Beta冲刺
    多喝热水【Beta】Scrum meeting 4
    多喝热水【Beta】Scrum meeting 3
    多喝热水【Beta】Scrum meeting 2
    多喝热水【Beta】Scrum meeting 1
    多喝热水 实验九 团队作业5:团队项目编码与Alpha冲刺
    多喝热水 [Alpha] Scrum Meeting 7
    多喝热水 [Alpha] Scrum Meeting 6
  • 原文地址:https://www.cnblogs.com/hf-z/p/6020896.html
Copyright © 2011-2022 走看看