2. Shiro 身份驗證

本篇參考張開濤《跟我學Shiro》

身份驗證

簡單來說就是在應用中誰能證明他就是本人。一般提供他們的身份信息來表明他就是他本人,比如說提供身份證,用戶名/密碼來證明。

在 Shiro 中,用戶需要提供 principals 身份和 credentials 證明給 Shiro ,從而應用能驗證用戶身份;

  1. principals:身份,就是主體的標識屬性,可以是任何東西,比如用戶名、郵箱等等,只要是唯一即可。一個主體可以有多個身份,但只能有一個身份證明,一般是用戶名/密碼/手機號。
  2. credentials:證明/憑證,就是隻有主體才知道的安全信息,比如密碼。最常見的 principals 和 credentials 組合就是用戶名和密碼了。

代碼實現

1. 添加依賴

首先添加 junit、common-logging及shiro-core 的依賴:

	<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
    </dependencies>

2. 準備 shiro.ini 文件

此處使用 ini 配置文件,配置一下用戶的基本信息

[users]
javaboy=123

3. 測試代碼

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Assert;

/**
 * @Author: 紅顏禍水nvn <[email protected]>
 * @Description: CSDN <https://blog.csdn.net/qq_43647359>
 */
public class TestHelloWorld {
    public static void main(String[] args) {
        // 1.獲取 SecurityManager 工廠,此處使用 ini 配置文件初始化 SecurityManager
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 2.得到 SecurityManager 實例,並綁定給 SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        // 3.得到 Subject 及創建用戶名/密碼身份驗證 Token
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("javaboy", "123");

        try {
            // 4.登陸(身份驗證)
            subject.login(token);
        } catch (AuthenticationException e) {
            // 5.身份驗證失敗
            System.out.println("用戶名或密碼錯誤");
        }
        // 斷言用戶已經登陸
        Assert.assertEquals(true,subject.isAuthenticated());
        // 6.退出
        subject.logout();
        System.out.println("登陸成功且退出!");
    }
}
  1. 首先通過 new IniSecurityManagerFactory 指定一個 ini 配置文件來創建一個 SecurityManager 工廠;

  2. 接着獲取 SecurityManager 並綁定到 SecurityUtils,這個一個全局設置,設置一次即可。

  3. 接着通過 SecurityUtils 得到 Subject,它會自動綁定到當前線程;如果 Web 環境在請求結束時需要解除綁定;然後獲取身份驗證的 Token,如用戶名/密碼;

  4. 調用 subject.login 方法進行登陸,它會自動委託給 SecurityManager.login 方法進行登陸,也就是說subject.login 實際上是 SecurityManger.login 方法來執行登陸操作的。

  5. 如果身份驗證錯誤會出觸發 AuthenticationException 異常,常見的有:

    DisabledAccountException:禁用的賬號
    LockedAccountException:鎖定的賬號
    UnknownAccountException:錯誤的賬號
    ExcessiveAttemptsException:登陸失敗次數過多
    IncorrectCredentialsException:錯誤的憑證
    ExpiredCredentialsException:過期的憑證

  6. 最後可以調用 subject.logout 退出。

身份認證流程

在這裏插入圖片描述

  1. 首先調用 Subject.login(token) 進行登陸,它會自動委託給 Security Manager ,調用之前必須通過 SecurityUtils.setSecurityManager() 來設置。
  2. SecurityManager 負責真正的身份驗證邏輯;它會交給 Authenticator 進行身份驗證;
  3. Authenticator 纔是真正的身份校驗者。Shiro API 的核心身份認證入口點,此處可以自定義實現方案。
  4. Authenticator 可能會交給相應的 AuthenticationStrategy 進行多 Realm 身份校驗,默認 ModularRealmAuthenticator 會調用 AuthenticationStrategy 繼續多 Realm 身份校驗;
  5. Authenticator 會把相應的 token 傳入 Realm 中,從 Realm 中拿到身份驗證信息如果拿到了返回的信息或者中途沒有拋出異常表示身份驗證成功了。此處可以配置多個 Realm,將按照相應的順序及策略進行訪問。

Realm 的簡單介紹

Realm:我們可以稱作 “域”,Shiro 從 Realm 獲取安全數據。

簡單來說就是 SecurityManager 要驗證用戶身份,那麼它就需要從 Realm 中獲取相應的用戶進行比對來確認用戶身份是否合法;

當然也需要從 Realm 中得到用戶相應的權限進行驗證用戶是否能夠操作;

可以將 Realm 看作 DataSource,就是安全數據源。例如我們之前的 ini 配置文件方式就是這種方式。

org.apache.shiro.realm.Realm 接口如下:

在這裏插入圖片描述

// 返回一個唯一的 Realm 名字
String getName(); 
// 判斷此 Realm 是否支持此 Token
boolean supports(AuthenticationToken token);
// 根據Token 獲取認證信息
AuthenticationInfo 
getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

自定義 Realm

我們可以自定義一個類來實現 Realm 接口:

import org.apache.shiro.authc.*;
import org.apache.shiro.realm.Realm;

/**
 * @Author: 紅顏禍水nvn <[email protected]>
 * @Description: CSDN <https://blog.csdn.net/qq_43647359>
 */
public class MyRealm implements Realm {
    @Override
    public String getName() {
        return "MyRealm";
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        // 僅僅支持 UsernamePassworkToken 類型的 Token
        return token instanceof UsernamePasswordToken;
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 通過 token.getPrincipal 拿到用戶名
        String username = (String) token.getPrincipal();
        // 通過 token.getCredentials 拿到密碼
        String password = new String((char[]) token.getCredentials());
        if (!"javaboy".equals(username)) {
            // 用戶名錯誤
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            // 密碼錯誤
            throw new IncorrectCredentialsException();
        }
        // 如果身份校驗成功,返回一個 AuthenticationInfo
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

ini 配置文件指定自定義 Realm 實現,首先創建一個 shiro-realm.ini 文件

# 聲明一個 Realm
myRealm=org.shiro.test.MyRealm
# 指定 securityManager 的 realms 實現
securityManager.realms=$myRealm

通過 $name 來引入自定義的 realm

測試時只需要將之前加載的 shiro.ini 配置文件修改一下即可。
在這裏插入圖片描述

多 Realm 配置

還是在 ini 配置文件中進行修改

# 聲明一個 realm
myRealm1=org.shiro.test.MyRealm1
myRealm2=org.shiro.test.MyRealm2
# 指定 securityManager 的 realms 實現
securityManager.realms=$myRealm1,$myRealm2

securityManager 會按照 realms 指定的順序進行身份驗證。我們也可以使用顯示指定順序的方式指定 Realm 的順序,如果刪除 “securityManager.realms=myRealm1,myRealm1,myRealm2”,那麼 securityManager 會按照 realm 聲明的順序進行使用,但是當我們顯示指定 realm 後,其他沒有指定 realm 將被忽略。例如:“securityManager.realms=$myRealm1”,那麼 myRealm2 不會被自動設置進去。

測試同上一樣。

Shiro 默認提供的 Realm

在這裏插入圖片描述

主要的實現如下:

  • org.apache.shiro.realm.text.IniRealm:

    [users]用來指定用戶名/密碼及其角色;
    [roles]用來指定角色權限信息;

  • org.apache.shiro.realm.text.PropertiesRealm:

    user.username=password,role1,role2 指定用戶名/密碼及其角色;
    role.role1=permission1,permission2 指定角色及權限信息;

  • org.apache.shiro.realm.jdbc.JdbcRealm:

    通過 sql 查詢相應的信息:

// 獲取用戶密碼
select password from users where username=?
// 獲取用戶密碼及鹽
select password,password_salt from users where username=?
// 獲取用戶角色
select role_name from user_roles where username=?
// 獲取角色對應的權限信息
select permission from roles_permissions where role_name=?

Authenticator 及 AuthenticationStrategy

Authenticator 的作用就是驗證用戶賬號,是 Shiro API 中身份驗證核心的入口點:

public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)  throws AuthenticationException;

如果驗證成功,將返回 AuthenticationInfo 驗證信息,信息中包含了身份和憑證;如果驗證失敗將拋出相應的 AuthenticationException 異常。

SecurityManager 接口繼承了 Authenticator ,另外還有一個 ModularRealmAuthenticator 實現,其委託給多個 Realm 進行驗證,驗證規則通過 AuthenticationStrategy 接口指定,默認提供的實現:

  • FirstSuccessfulStrategy:只要有一個 Realm 驗證成功,只返回第一個 Realm 身份驗證成功的認證信息,其他的都會被忽略。
  • AtLeastOneSuccessfulStrategy:只要有一個 Realm 驗證成功即可,和 FirstSuccessfulStrategy 不同,返回所有 Realm 身份驗證成功的認證信息。
  • AllSuccessfulStrategy:所有的 Realm 驗證成功纔算成功,且返回所有的 Realm 身份驗證成功的認證信息,如果有一個失敗就全部失敗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章