本篇參考張開濤《跟我學Shiro》
文章目錄
身份驗證
簡單來說就是在應用中誰能證明他就是本人。一般提供他們的身份信息來表明他就是他本人,比如說提供身份證,用戶名/密碼來證明。
在 Shiro 中,用戶需要提供 principals 身份和 credentials 證明給 Shiro ,從而應用能驗證用戶身份;
principals:身份,就是主體的標識屬性
,可以是任何東西,比如用戶名、郵箱等等,只要是唯一即可。一個主體可以有多個身份,但只能有一個身份證明,一般是用戶名/密碼/手機號。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("登陸成功且退出!");
}
}
-
首先通過 new IniSecurityManagerFactory 指定一個 ini 配置文件來創建一個 SecurityManager 工廠;
-
接着獲取 SecurityManager 並綁定到 SecurityUtils,這個一個全局設置,設置一次即可。
-
接着通過 SecurityUtils 得到 Subject,它會自動綁定到當前線程;如果 Web 環境在請求結束時需要解除綁定;然後獲取身份驗證的 Token,如用戶名/密碼;
-
調用 subject.login 方法進行登陸,它會自動委託給 SecurityManager.login 方法進行登陸,也就是說
subject.login 實際上是 SecurityManger.login 方法來執行登陸操作的。
-
如果身份驗證錯誤會出觸發 AuthenticationException 異常,常見的有:
DisabledAccountException:禁用的賬號
LockedAccountException:鎖定的賬號
UnknownAccountException:錯誤的賬號
ExcessiveAttemptsException:登陸失敗次數過多
IncorrectCredentialsException:錯誤的憑證
ExpiredCredentialsException:過期的憑證
-
最後可以調用 subject.logout 退出。
身份認證流程
- 首先調用
Subject.login(token)
進行登陸,它會自動委託給Security Manager
,調用之前必須通過SecurityUtils.setSecurityManager()
來設置。 SecurityManager 負責真正的身份驗證邏輯
;它會交給 Authenticator 進行身份驗證;Authenticator 纔是真正的身份校驗者
。Shiro API 的核心身份認證入口點,此處可以自定義實現方案。- Authenticator 可能會交給相應的 AuthenticationStrategy 進行多 Realm 身份校驗,默認 ModularRealmAuthenticator 會調用 AuthenticationStrategy 繼續多 Realm 身份校驗;
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=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 身份驗證成功的認證信息,如果有一個失敗就全部失敗。