Apache Shiro系列教程之二:十分鐘上手Shiro

在本教程中,咱們會寫一個簡單的、僅僅輸出一些內容命令行程序,從而對Shiro有一個大致的感受。html

1、準備工做

本教程須要Java1.5+,而且咱們用Maven生成項目,固然Maven不是必須的,你也能夠經過導入Shiro jar包的方式、或使用Ant、Ivy,喜歡哪一種就用哪一種。java

開始以前,肯定你的Maven版本爲2.2.1+(若是你用的是Maven的話),用mvn --version肯定Maven的版本。web

如今,咱們將正式開始。首先新建一個文件夾,好比說shiro-tutorial,而後將下面的Maven pom.xml文件放到該文件夾下。數據庫

pom.xmlapache

 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>

2、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 }

在往下進行以前,先測試一下是否能夠運行。服務器

3、測試運行

首先進入項目根目錄(本教程爲shiro-tutorial目錄,即pom.xml所在的目錄),打開控制檯,輸入命令:session

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運行程序。

4、使用Shiro

在Shiro中有一個很是重要的組件--SecurityManager,Shiro的全部功能幾乎都與這個組件相關。這樣Java security相似,可是和java.lang.SecurityManager是不同的。

咱們會在後續教程中對SecurityManager作詳細介紹。可是如今咱們就要明確一點:SecurityManager是Shrio的核心組件,而且任何一個應用都要有一個SecurityManager纔可使用Shiro的其餘功能。全部,在本教程中必需要作的事就是實例化一個SecurityManager。

5、配置

即便咱們能夠直接實例化一個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等。

6、引用配置文件

如今咱們已經有了一個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容器。

7、使用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.

8、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);
    }
}

 

9、總結

但願經過本教程,你能夠知道如何設置shiro,而且瞭解Subject和SecurityManager這兩個基本概念。

可是這只是一個很是很是簡單的應用。你可能會問「若是我不想要INI而想用更加複雜的數據源該怎麼作?」

爲了解答這個問題,咱們須要更加深刻地瞭解一下shiro的結構,我門將在後面的章節中學習。

相關文章
相關標籤/搜索