1. 認證
身份認證,就是判斷一個用戶是否為合法用戶的處理過程。最常用的簡單身份認證方式是系統通過核對用戶輸入的用戶名口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。
2. Shiro 中認證的關鍵對象
- Subject 主體:訪問系統的用戶,主體可以是用戶、程序等等,進行認證的都稱為主體;
- Principal 身份信息:是主體(Subject)進行身份認證的標識,標識必須具有唯一性,如用戶名、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)
- Credential 憑證信息:是只有主體自己知道的安全信息,如密碼、證書等。
3. 認證流程
4. 認證開發
4.1 模擬數據庫中已存儲用戶信息【創建 shiro.ini 文件】
[users] wangwu=123 zhangsan=123456 lisi=789
4.2 開發認證代碼
public class TestAuthenticator { public static void main(String[] args) { // 1. 創建安全管理器 DefaultSecurityManager securityManager = new DefaultSecurityManager(); // 2. 給安全管理器設置 realm 【Realm 需要獲取用戶認證的數據源信息】 securityManager.setRealm(new IniRealm("classpath:shiro.ini")); // 3. SecurityUtils 設置安全管理器 SecurityUtils.setSecurityManager(securityManager); // 4. 獲取關鍵對象 Subject 主體 Subject subject = SecurityUtils.getSubject(); // 5. 創建令牌 UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123"); // 用戶認證 try { System.out.println("認證狀態: " + subject.isAuthenticated()); subject.login(token); System.out.println("認證狀態: " + subject.isAuthenticated()); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("認證失敗: 用戶名不存在~"); } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("認證失敗: 密碼錯誤~"); } } }
-
-
LockedAccountException(帳號被鎖定)
-
ExcessiveAttemptsException(登錄失敗次數過多)
-
public class SimpleAccountRealm extends AuthorizingRealm { //.......省略 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; SimpleAccount account = getUser(upToken.getUsername()); if (account != null) { if (account.isLocked()) { throw new LockedAccountException("Account [" + account + "] is locked."); } if (account.isCredentialsExpired()) { String msg = "The credentials for account [" + account + "] are expired"; throw new ExpiredCredentialsException(msg); } } return account; } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = getUsername(principals); USERS_LOCK.readLock().lock(); try { return this.users.get(username); } finally { USERS_LOCK.readLock().unlock(); } } }
3)自定義 Realm
由於 Realm
相當於 Datasource 數據源,SecurityManager 進行安全認證需要通過 Realm 獲取用戶身份數據,所以可以自定義 Realm 獲取用戶身份的信息;
public class CustomerRealm extends AuthorizingRealm { // 授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("===================="); return null; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); System.out.println("principal = " + principal); //根據身份信息使用jdbc mybatis查詢相關數據庫 if ("xiaochen".equalsIgnoreCase(principal)) { // 參數1:返回數據庫中的用戶名 參數2:返回數據庫中的正確密碼 參數3:提供當前的 realm 名字,this.getName() SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wangwu", "123", this.getName()); return simpleAuthenticationInfo; } return null; } }
4)使用自定義 Realm 進行認證
public class TestCustomerRealm { public static void main(String[] args) { // 1.創建安全管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); // 2.給安全管理器設置 realm 【數據庫中獲取用戶名和密碼】 defaultSecurityManager.setRealm(new CustomerRealm()); // 3.SecurityUtils 設置安全管理器 SecurityUtils.setSecurityManager(defaultSecurityManager); // 4.獲取用戶登錄的主體 Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken("wangwu", "123"); try { subject.login(token); } catch (UnknownAccountException e) { System.out.println("用戶名不正確"); e.printStackTrace(); } catch (IncorrectCredentialsException e) { System.out.println("用戶密碼不正確"); e.printStackTrace(); } } }
5) 使用 MD5 和 Salt
實際應用是將鹽和散列后的值存在數據庫中,自動realm從數據庫取出鹽和加密后的值由shiro完成密碼校驗。
A 自定義 MD5 + salt 的 Realm
public class CustomerMD5Realm extends AuthorizingRealm { /** * 一個主題可以有多個身份,參數是集合 principals;但是只有一個主身份; * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 獲取身份信息 String principal = (String) principals.getPrimaryPrincipal(); System.out.println("身份信息 = " + principal); return null; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String principal = (String) token.getPrincipal(); System.out.println("principal = " + principal); //根據身份信息使用jdbc mybatis查詢相關數據庫 if ("wangwu".equalsIgnoreCase(principal)) { // 參數1:返回數據庫中的用戶名 參數2:返回數據庫中的正確密碼 參數3:注冊時的隨機鹽 參數4:提供當前的 realm 名字,this.getName() SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "e4f9bf3e0c58f045e62c23c533fcf633", ByteSource.Util.bytes("X0*7ps"), this.getName()); return simpleAuthenticationInfo; } return null; } }
B 使用 MD5 + Salt 進行認證處理
public class TestCustomerMD5Authenticator { public static void main(String[] args) { // 1. 創建安全管理器 DefaultSecurityManager securityManager = new DefaultSecurityManager(); // 2. 給安全管理器設置 realm 【Realm 需要獲取用戶認證的數據源信息】 CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm(); // 設置 realm 使用的 hash 憑證匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("md5"); // 設置使用的 Hash 算法 credentialsMatcher.setHashIterations(1024); // 需要散列多少次 customerMD5Realm.setCredentialsMatcher(credentialsMatcher); securityManager.setRealm(customerMD5Realm); // 3. SecurityUtils 設置安全管理器 SecurityUtils.setSecurityManager(securityManager); // 4. 獲取關鍵對象 Subject 主體 Subject subject = SecurityUtils.getSubject(); // 5. 創建令牌 UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123"); // 用戶認證 try { System.out.println("認證狀態: " + subject.isAuthenticated()); subject.login(token); System.out.println("認證狀態: " + subject.isAuthenticated()); } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("認證失敗: 用戶名不存在~"); } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("認證失敗: 密碼錯誤~"); } } }
注意:深入了解 Shiro 的認證流程,可以 debug 運行程序,查看源代碼進行學習深究!