shiro目錄:
shior作爲輕量級的權限管理框架,相較於SpringSecurity框架,沒有其強大的功能,但在是學習和實際使用中,shiro可以滿足基本的使用,包括有身份驗證、授權、加密的功能
shiro的功能介紹,分爲以下幾塊(官方介紹,看不懂 的可以稍微瞅一眼,然後基礎代碼看,回頭再看更加容易理解)
Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Manager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web 支持,可以非常容易的集成到 Web 環境;
Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
Concurrency:shiro 支持多線程應用的併發驗證,即如在一個線程中開啓另一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄
詳解:
- Authentication:
- 官方定義:Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.
翻譯:身份驗證,通常是在登錄是,通過對用戶的信息(賬號、密碼)進行驗證,如果登錄失敗會拋出異常
- 流程圖:
Subject:表示“用戶”,表示當前執行的用戶。Subject 實例全部都綁定到了一個 SecurityManager 上,當和 Subject 交互時,它是委託給 SecurityManager 去執行的
SecurityManager:Shiro 結構的心臟,協調它內部的安全組件(如登錄,授權,數據源等)。當整個應用配置好了以後,大多數時候都是直接和 Subject 的 API 打交道。
Realm:數據源,也就是抽象意義上的 DAO 層。它負責和安全數據交互(比如存儲在數據庫的賬號、密碼,權限等信息),包括獲取和驗證。Shiro 支持多個 Realm,但是至少也要有一個。Shiro 自帶了很多開箱即用的 Reams,比如支持 LDAP、關係數據庫(JDBC)、INI 和 properties 文件等。但是很多時候我們都需要實現自己的 Ream 去完成獲取數據和判斷的功能
登錄流程:Subject 執行 login 方法,傳入登錄的用戶名和密碼,然後 SecurityManager將這個 login 操作委託給內部的登錄模塊,登錄模塊就調用 Realm 去獲取安全的用戶名和密碼,然後對比,一致則登錄,不一致則登錄拋出異常
- 核心代碼
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
- 通過用戶名和密碼生成token,調用Subject.login 進行登錄驗證(調用shiro的Realm 類進行驗證)
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
public class ShiroLogin {
public static String login(UsernamePasswordToken token){
// 從SecurityUtils裏邊創建一個 subject
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知賬戶";
} catch (IncorrectCredentialsException ice) {
return "密碼不正確";
} catch (LockedAccountException lae) {
return "賬戶已鎖定";
} catch (ExcessiveAttemptsException eae) {
return "用戶名或密碼錯誤次數過多";
} catch (AuthenticationException ae) {
return "用戶名或密碼不正確!";
}
if (subject.isAuthenticated()) {
return "登錄成功";
} else {
token.clear();
return "登錄失敗";
}
}
}
- Realm類包含兩個方法doGetAuthorizationInfo和doGetAuthenticationInfo
- doGetAuthorizationInfo方法用來獲取用戶權限信息
- doGetAuthenticationInfo方法是獲取用戶登錄憑證信息
- 代碼如下:
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
private static final transient Logger log = LoggerFactory.getLogger(CustomRealm.class);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("------獲取用戶身份信息------");
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> stringSet = new HashSet<>();
stringSet.add("user:show");
stringSet.add("user:admin");
info.setStringPermissions(stringSet);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("-------身份認證方法--------");
String userName = (String) authenticationToken.getPrincipal();
String userPwd = new String((char[]) authenticationToken.getCredentials());
//模擬 根據用戶名從數據庫獲取密碼
String password = "2415b95d3203ac901e287b76fcef640b";
if (userName == null) {
throw new AccountException("用戶名不正確");
} else if (!userPwd.equals(userPwd)) {
throw new AccountException("密碼不正確");
}
//交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配
String name = getName();
System.out.println("name:"+name);
return new SimpleAuthenticationInfo(userName, password,
ByteSource.Util.bytes(userName + "salt"), getName());
}
}
執行登錄操作:
/**
* 登錄操作
* @param username
* @param password
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
// 在認證提交前準備 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 執行認證登陸
return shiroLogin.login(token);
}
測試:
1.輸入密碼錯誤
2.輸入密碼正確
歡迎留言一起討論
github地址:shiro