JavaWEB開發框架:Shiro

經過了三個月的實習,在javaWEB開發的過程中,學習到了一些新的知識,特此記錄一下學習到的一種新的WEB開發當中經常使用的用戶認證和受權的安全框架,Shiro。java

 

首先,要先知道shiro這個框架主要在WEB開發當中的定位,其主要的定位是用於作用戶登陸和權限控制的一個安全框架,能夠幫助咱們更加安全的完成用戶的登陸以及受權的相關過程,而且其使用和配置都十分的簡便,還能夠集成到SpringMVC當中,而且還能配合各種數據庫的使用,包括mysql、nosql等,因此可謂十分強大但又很簡練的安全框架。mysql

 

要開始學習shiro的使用,咱們要先引入一些在shiro當中的一些核心功能的概念:web

1.Authentication(身份驗證):簡稱爲「登陸」,即驗證當前登陸的用戶是不是咱們項目當中的合法用戶。算法

2.Authorization(受權):不一樣級別的合法用戶的訪問權限的控制過程,即決定哪些用戶擁有哪些權限去訪問某些的資源。sql

3.Session Management(會話管理):管理用戶特定的會話,即便在非 Web 或 EJB 應用程序。數據庫

4.Cryptography(加密):經過使用加密算法保持數據安全apache

shiro因爲其定位的因素,本文主要介紹其中的兩個核心方法:認證(Authentication)和受權(Authorization)安全

 

在介紹功能以前,咱們還要簡單的介紹一下在shiro框架當中的主要使用的組件的概念:數據結構

1.Subject : 正與系統進行交互的對象,在web開發當中,能夠理解成每一臺想要訪問web的用戶,都是一個subject。架構

2.SecurityManager:顧名思義,就是用於安全事務的管理的對象,Shiro 架構的核心,其用來協調內部各安全組件,管理內部組件實例,並經過它來提供安全管理的各類服務。當 Shiro 與一個 Subject  進行交互時,實質上是幕後的 SecurityManager  處理全部繁重的 Subject 安全操做。

3.Realms :本質上是一個特定安全的 DAO,即一個安全的數據源。當配置 Shiro  時,必須指定至少一個 Realm  用來進行身份驗證和/或受權。Shiro 提供了多種可用的 Realms 來獲取安全相關的數據。如關係數據庫(JDBC),INI 及屬性文件等。能夠定義本身 Realm  實現來表明自定義的數據源。

 

對以上這些概念有了一些理解以後,就基本掌握了shiro的大致概念了,那麼接下來,咱們來了解shiro如何使用,二話不說,先上咱們的maven依賴文件:

<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>bjtu.wellhold</groupId>
  <artifactId>Shiro</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>Shiro</name>
  <url>http://maven.apache.org</url>

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

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.9</version>
      <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    
  </dependencies>
</project>

測試框架咱們使用的是junit,shiro的版本咱們使用的是1.2.3,這裏要額外提一句,版本的不一樣,對一些功能的實現會有不兼容的現象(報錯),因此必定要注意版本的問題。

 

在上頭介紹概念的時候,有提到過Realms 的概念,它是一個要驗證的數據源,能夠把它理解成一個安全的用於存儲程序合法用戶信息——用戶名、密碼、以及該用戶的權限的倉庫,全部用戶提交的帳戶信息都須要到這裏頭進行驗證,看看用戶提交的帳戶信息是否存在於Realms裏頭,一斷定當前想要登陸的用戶是不是程序的合法用戶,若是是合法用戶,那麼還能夠獲取到該用戶的一個安全權限,即該用戶能夠訪問什麼內容,不能夠訪問什麼內容,可能解釋的有點繁瑣,也不是很全面,可是這樣的解釋比較利於理解。在介紹Realms 概念的時候,有提到Realms 的數據源能夠是JDBC,也能夠是.ini文件,那麼接下來咱們就分別對兩種形式進行介紹。

 

1.經過.ini做爲信息數據源

在shiro1.2.3.版本以後,在咱們沒有指定相應的Realms的實現類的時候,shiro會自動的爲咱們生成一個iniRealm實例供程序使用,因此咱們只須要指定咱們的信息源.ini文件便可,下邊是咱們的.ini文件內容:

#自定義數據源 格式 用戶名=密碼

[users]

Floder=Floder

內容很簡單,只是指定了一個用戶名爲Floder,密碼爲Floder的用戶,由於這裏只涉及講解身份認證,因此並無對這個用戶的權限內容進行編寫。接下來看看咱們的代碼:

    //認證過程
    public void demo1()
    {
        //該demo中沒有配置realm,是由於Shiro,默認提供了一個叫IniRealm的實例在進行的
        //在1.2.3版本以後纔有的
        
        //設置securityManage環境
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager manager=factory.getInstance();
        SecurityUtils.setSecurityManager(manager);
        
        //模擬subject
        Subject subject=SecurityUtils.getSubject();
        UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder");
        try{
            subject.login(token);
        }catch(AuthenticationException e)
        {
            e.printStackTrace();
        }
        
        boolean isOK=subject.isAuthenticated();
        System.out.println("用戶是否登陸:"+isOK);
        
        //模擬用戶退出登陸
        subject.logout();
        isOK=subject.isAuthenticated();
        System.out.println("用戶是否登陸:"+isOK);
    }

代碼當中,首先獲取到一個SecurityManager的工廠實例,而且爲這個實例指定了咱們的數據源文件,以後經過工廠實例生成了一個SecurityManager的實例,再爲SecurityUtils去配置SecurityManager,這樣咱們的程序就有一個SecurityManager用於管理全部安全行爲的過程了,並對全部的安全行爲負責(這裏提到的安全行爲主要指的是身份認證和受權過程)。以後咱們經過模擬一個subject和token(令牌)去模擬用戶登陸的過程,經過執行subject.login,將令牌信息提交到subject以後,再執行subject.isAuthenticated(),對提交的令牌進行身份認證,即到咱們配置的.ini文件當中去查找是否存在這個合法用戶,若是存在則返回true,若是不存在則返回false,執行結果以下:

 

接下來咱們再來看看的受權過程,這時候咱們要在.ini文件當中配置到合法用戶的相關權限:

#用戶對應的角色

#用戶Floder擁有角色role1和role2

[users]

Floder = Floder,role1,role2

floder=floder,role2

#角色對應的資源權限

[roles]

#權限標識符符號規則 資源:操做:實例 user:create:01 表示對用戶資源實例01進行create操做

#user:create 表示對資源進行create操做,至關於user:create:*

#user:*:01 表示對用戶資源實例01進行全部操做

role1=user:create,user:update

role2 = user:create

在.ini文件當中,咱們配置了兩個合法用戶,而且咱們制定了相應的roles,即角色,相信熟悉較爲高級的數據庫的人對這個概念不陌生,角色的不一樣,擁有的不一樣權限,在咱們的demo中,角色1有建立用戶和更新用戶的權限,而角色2則只有建立用戶的權限。接下來看咱們的受權demo的代碼:

    @Test
    //受權過程
    public void demo2()
    {
        //該demo中沒有配置realm,是由於Shiro,默認提供了一個叫IniRealm的實例在進行的
        //在1.2.3版本以後纔有的
        
        //設置securityManage環境
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
        SecurityManager manager=factory.getInstance();
        SecurityUtils.setSecurityManager(manager);
    
        Subject subject = SecurityUtils.getSubject();
//        Subject subject2 = SecurityUtils.getSubject();
        //提交認證是說攜帶的信息儲存在 Token 中
        UsernamePasswordToken token = new UsernamePasswordToken("Floder", "Floder");
//        UsernamePasswordToken token2 = new UsernamePasswordToken("floder", "floder");
        //模擬獲取登錄
        try {
        subject.login(token);
        } catch (AuthenticationException e) {
        e.printStackTrace();
        }
        
        //基於角色判斷
        //判斷認證用戶是否有role1角色
        boolean isHasRole=subject.hasRole("role1");
        System.out.println("判斷當前用戶是否有role1角色:"+isHasRole);
        
        //判斷當前用戶是否擁有多個角色
        boolean isHasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2"));
        System.out.println("判斷當前用戶是否有多個角色:"+isHasAllRoles);
        
        //基於資源判斷
        //判斷當前用戶是否能對該資源進行單一操做
        boolean isPermitted=subject.isPermitted("user:create");
        System.out.println("判斷當前用戶是否能對該資源進行單一操做:"+isPermitted);
        
        //判斷已認證用戶的資源是否擁有對應的多個操做
        boolean isPermittedAll = subject.isPermittedAll("user:create","user:update");
        System.out.println("判斷已認證用戶的資源是否擁有對應的多個操做 "+isPermittedAll);
    }

在代碼當中,當前合法用戶進行了各類角色和受權的行爲判別,即管理當前用戶所具備的安全權限。運行結果以下:

將咱們的token換成token2進行subject.login,再看運行結果:

符合咱們在.ini文件當中指定的內容。到此,咱們基於.ini配置文件的身份認證和受權管理過程就講的差很少了,由於是使用的shiro提供的自動生成的realm,因此所須要編寫的東西仍是相對簡單的,接下來咱們來學習經過咱們自定義的realm來作身份認證和受權的過程。

 

2.經過自定義Realms做爲信息數據源

首先先看咱們自定義的Realms實現類的代碼:

package bjtu.wellhold.Shiro;

import java.util.ArrayList;
import java.util.List;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {

    @Override
    public void setName(String name) {
    super.setName("userName");
    }
    
    //受權(依賴於認證信息)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        String userId = (String) principals.getPrimaryPrincipal();
        //模擬從數據庫得來的角色信息
        List<String> listPermission = new ArrayList<String>();
        listPermission.add("user:create");
        listPermission.add("user:update");
        
         //將權限信息保存到AuthorizationInfo中
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for(String permission:listPermission){
            simpleAuthorizationInfo.addStringPermission(permission);
        }
        return simpleAuthorizationInfo;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        
        //獲得token的用戶信息
        // 從token中取出身份信息, 即用戶的username
        String userId = (String) token.getPrincipal();
        System.out.println("token傳入的用戶名:"+userId);
        String password = new String((char[])token.getCredentials());
        System.out.println("token傳入的密碼:"+password);
        
        //能夠到數據庫當中的某張表查詢是否存在該合法用戶信息
        
        //模擬數據庫返回數據,存在就返回一個credentials
        String username1 = "Floder";//數據庫當中存在的用戶名
        String password1 = "Floder";//數據庫當中存在的密碼
        if(!username1.equals(userId)) throw new UnknownAccountException("沒有這個用戶!");
        else if(!password1.equals(password)) throw new IncorrectCredentialsException("密碼錯誤!");
        else
        {
            //實際上在返回SimpleAuthenticationInfo的時候
            //subject.login當中的token仍是會和SimpleAuthenticationInfo當中的用戶名密碼進行匹配一次
            //不過爲了可控的拋出exception信息,咱們仍是在方法中就進行驗證用戶名和密碼
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userId, password1, this.getName());
            return info;
        }
    }

}

在代碼中,咱們自定義了一個實現類叫UserRealm,它繼承了父類AuthorizingRealm,繼承只要,須要實現兩個方法,分別是身份認證方法:doGetAuthenticationInfo,以及受權方法:doGetAuthorizationInfo。

咱們首先來講身份認證方法的實現邏輯,咱們先來看看咱們的demo函數的寫法,與使用shiro提供的自定義Realm當中的demo函數的邏輯類似:

//採用自定義realm
    @Test
    public void demo3()
    {
        //設置securityManage環境
        IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro-single-Aurealm.ini");
        SecurityManager manager=factory.getInstance();
        SecurityUtils.setSecurityManager(manager);
        
        //模擬subject
        Subject subject=SecurityUtils.getSubject();
        UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder");
        try{
            subject.login(token);
        }catch(AuthenticationException e)
        {
            e.printStackTrace();
        }
        
        boolean isOK=subject.isAuthenticated();
        System.out.println("用戶是否登陸:"+isOK);
        
        //模擬用戶退出登陸
        subject.logout();
        isOK=subject.isAuthenticated();
        System.out.println("用戶是否登陸:"+isOK);
    }

demo函數當中,仍是以往的套路,首先生成一個工廠的實例,在生成工廠實例的過程中指定.ini配置文件,可是這一次,不在.ini內寫用戶信息,而是在.ini內寫自定義的Realm信息:

userRealm=bjtu.wellhold.Shiro.UserRealm
securityManager.realms=$userRealm

在配置完SecurityManage環境以後,咱們執行subject.login,這個時候,會自動調用到自定義的Realm類裏頭的doGetAuthenticationInfo方法,而且把token傳過去,這時候咱們就能夠在doGetAuthenticationInfo方法當中作用戶的身份認證,信息源能夠是mysql等數據庫,而且還能夠對傳入的token進行用戶名密碼的分別認證,而後若是不屬於合法信息用戶還能夠分別跑出不一樣的異常信息進行提示,在代碼當中的註釋也寫的很明白了,這裏就再也不進行贅述了。

看完自定義類的身份認證,咱們再來看自定義類的受權過程,首先看受權的demo代碼:

@Test
    public void demo4() {
        
        IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro-single-Aurealm.ini");
        SecurityManager manager=factory.getInstance();
        SecurityUtils.setSecurityManager(manager);
        
        //模擬subject
        Subject subject=SecurityUtils.getSubject();
        UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder");
        try{
            subject.login(token);
        }catch(AuthenticationException e)
        {
            e.printStackTrace();
        }
        
        
        //基於資源判斷
        //判斷當前用戶是否能對該資源進行單一操做
        boolean isPermitted=subject.isPermitted("user:create");
        System.out.println("判斷當前用戶是否能對該資源進行單一操做:"+isPermitted);
        
        //判斷已認證用戶的資源是否擁有對應的多個操做
        boolean isPermittedAll = subject.isPermittedAll("user:create","user:update");
        System.out.println("判斷已認證用戶的資源是否擁有對應的多個操做 "+isPermittedAll);

    }

對傳入的token進行了身份認證以後,在進行受權確認,在doGetAuthorizationInfo的方法當中,對token當中的信息,進行了受權信息的查詢(能夠從配置文件當中查詢,也能夠從數據庫當中查詢)查詢以後經過simpleAuthorizationInfo實例對象返回到demo函數當中,具體的能夠經過代碼當中的註釋很明瞭的看出,這裏就不進行贅述了。

到此爲止,基於.ini文件配置以及基於自定義的Realm的方式使用Shiro進行用戶登陸的身份認證和受權的demo就講解完畢了,可能總結的比較片面,描述的也比較簡單,若有描述不正確的地方,還請聯繫本人,相互交流一下。

相關文章
相關標籤/搜索