參考:
https://jinnianshilongnian.iteye.com/blog/2018936
http://how2j.cn/k/shiro/shiro-tutorial/1720.html#nowhere
RBAC 是當下權限系統的設計基礎,同時有兩種解釋:
1.: Role-Based Access Control,基於角色的訪問控制
即,你要能夠刪除產品,那麼當前用戶就必須擁有產品經理這個角色
2.:Resource-Based Access Control,基於資源的訪問控制
即,你要能夠刪除產品,那麼當前用戶就必須擁有刪除產品這樣的權限
開源權限管理項目:
Spring Security
Apache Shiro
Apache Shiro是Java的一個安全框架,基本功能點如圖:
Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
Session Manager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
Web Support:Web支持,可以非常容易的集成到Web環境;
Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啓另一個線程,能把權限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了。
以用戶登錄爲例子,
應用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是Subject;其每個API的含義:
Subject:主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委託給SecurityManager;可以把Subject認爲是一個門面;SecurityManager纔是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。
最簡單的一個Shiro應用:
1、應用代碼通過Subject來進行認證和授權,而Subject又委託給SecurityManager;
2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的用戶及其權限進行判斷。
一.接下來入門一個通過ini的簡單登錄的例子:
1.使用idea創建一個普通的java項目
新手可以參照:https://blog.csdn.net/oschina_41790905/article/details/79475187
2.引入兩個jar包
新手關於jar包引入可以參考https://blog.csdn.net/qq_28289405/article/details/82216847
3.在src目錄下新建 shiro.ini
用戶數據 此處充當realm域
#定義用戶
[users]
#用戶名 zhang3 密碼是 12345
zhang3 = 12345
#用戶名 li4 密碼是 abcde
li4 = abcde
3. 在src目錄下創建 包結構,創建User.class
public class User {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4. 創建 TestShiro
準備3個用戶,前兩個能在 shiro.ini 中找到,第3個不存在
然後測試登錄
public class TestShiro {
public static void main(String[] args) {
User zhang3 = new User();
zhang3.setName("zhang3");
zhang3.setPassword("12345");
User li4 = new User();
li4.setName("li4");
li4.setPassword("abcde");
User wang5 = new User();
wang5.setName("wang5");
wang5.setPassword("wrongpassword");
List<User> users = new ArrayList<>();
users.add(zhang3);
users.add(li4);
users.add(wang5);
//登陸每個用戶
for (User user : users) {
if(login(user))
System.out.printf("%s \t成功登陸,用的密碼是 %s\t %n",user.getName(),user.getPassword());
else
System.out.printf("%s \t成功失敗,用的密碼是 %s\t %n",user.getName(),user.getPassword());
}
}
private static boolean login(User user) {
Subject subject = getSubject(user);
//如果已經登錄過了,退出
if (subject.isAuthenticated())
subject.logout();
//封裝用戶的數據
//token可以理解爲用戶令牌,登錄的過程被抽象爲Shiro驗證令牌是否具有合法身份以及相關權限。
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
try {
//將用戶的數據token 最終傳遞到Realm中進行對比
subject.login(token);
} catch (AuthenticationException e) {
//驗證錯誤
return false;
}
return subject.isAuthenticated();
}
private static Subject getSubject(User user) {
//加載配置文件,並獲取工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//獲取安全管理者實例
SecurityManager sm = factory.getInstance();
//將安全管理者放入全局對象,注入SecurityManager
SecurityUtils.setSecurityManager(sm);
//全局對象通過安全管理者生成Subject對象
Subject subject = SecurityUtils.getSubject();
return subject;
}
}
5. 運行查看結果
二、通過數據庫充當realm域
1.創建表
前面提到過,RBAC 的概念, RBAC會存在3 張基礎表: 用戶,角色,權限, 以及 2 張中間表來建立 用戶與角色的多對多關係,角色與權限的多對多關係。 用戶與權限之間也是多對多關係,但是是通過 角色間接建立的。
2. 添加jar包
簡單介紹一下幾個jar包:
commons-beanutils
是Apache開源組織提供的用於操作JAVA BEAN的工具包。使用commons-beanutils
,我們可以很方便的對bean對象的屬性進行操作。參考:https://blog.csdn.net/jianggujin/article/details/51104949
slf4j:Simple Logging Facade for Java,爲java提供的簡單日誌Facade。Facade門面,更底層一點說就是接口。它允許用戶以自己的喜好,在工程中通過slf4j接入不同的日誌系統。 參考:https://www.cnblogs.com/lujiango/p/8573411.html
因此slf4j入口就是衆多接口的集合,它不負責具體的日誌實現,只在編譯時負責尋找合適的日誌系統進行綁定。具體有哪些接口,全部都定義在slf4j-api中。它只提供一個核心slf4j api(就是slf4j-api.jar包),這個包只有日誌的接口,並沒有實現,所以如果要使用就得再給它提供一個實現了些接口的日誌包,比 如:log4j,common logging,jdk log日誌實現包等。
Jakarta Commons-logging(JCL)是apache最早提供的日誌的門面接口。提供簡單的日誌實現以及日誌解耦功能。 ---------------------------------------------------------------------------https://www.cnblogs.com/xiadongqing/p/5208824.htm
mysql-connector-java-5.0.8-bin JDBC驅動
3.
public class DatabaseRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//能進入到這裏,表示賬號已經通過驗證了
String userName =(String) principalCollection.getPrimaryPrincipal();
//通過DAO獲取角色和權限
Set<String> permissions = new DAO().listPermissions(userName);
Set<String> roles = new DAO().listRoles(userName);
//授權對象
SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
//把通過DAO獲取到的角色和權限放進去
s.setStringPermissions(permissions);
s.setRoles(roles);
return s;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//獲取賬號密碼
UsernamePasswordToken t = (UsernamePasswordToken) token;
String userName= token.getPrincipal().toString();
String password= new String( t.getPassword());
//獲取數據庫中的密碼
String passwordInDB = new DAO().getPassword(userName);
//如果爲空就是賬號不存在,如果不相同就是密碼錯誤,但是都拋出AuthenticationException,而不是拋出具體錯誤原因,免得給破解者提供幫助信息
if(null==passwordInDB || !passwordInDB.equals(password))
throw new AuthenticationException();
//認證信息裏存放賬號密碼, getName() 是當前Realm的繼承方法,通常返回當前類名 :databaseRealm
SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());
return a;
}
}
4.修改ini文件
[main]
databaseRealm=com.jxw.DatabaseRealm
securityManager.realms=$databaseRealm
5. 運行結果與之前相同
Maven項目入門
身份驗證,即在應用中誰能證明他就是他本人。一般提供如他們的身份ID一些標識信息來表明他就是他本人,如提供身份證,用戶名/密碼來證明。
在shiro中,用戶需要提供principals (身份)和credentials(證明)給shiro,從而應用能驗證用戶身份:
principals:身份,即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可。一個主體可以有多個principals,但只有一個Primary principals,一般是用戶名/密碼/手機號。
credentials:證明/憑證,即只有主體知道的安全值,如密碼/數字證書等。
最常見的principals和credentials組合就是用戶名/密碼了。接下來先進行一個基本的身份認證。
另外兩個相關的概念是之前提到的Subject及Realm,分別是主體及驗證主體的數據源。
基本的身份認證
1. 通過idea創建一個maven項目
項目結構如圖
2. 在pom.xml中添加如下的代碼,引入相關需要的jar包
<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>
3.創建shiro.ini 用戶身份/憑據
通過[users]指定了兩個主體:zhang/123、wang/123。
即zhang爲用戶名 123位密碼
[users]
zhang=123
wang=123
4.編寫測試用例 LoginTest.java
public class LoginTest {
@Test
public void test1(){
// 1、首先通過new IniSecurityManagerFactory並指定一個ini配置文件來創建一個SecurityManager工廠;
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2、接着獲取SecurityManager並綁定到SecurityUtils,這是一個全局設置,設置一次即可;
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 3、通過SecurityUtils得到Subject,其會自動綁定到當前線程;如果在web環境在請求結束時需要解除綁定;然後獲取身份驗證的Token,如用戶名/密碼;
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "1");
// 4、調用subject.login方法進行登錄,其會自動委託給SecurityManager.login方法進行登錄;
// 5、如果身份驗證失敗請捕獲AuthenticationException或其子類,常見的如: DisabledAccountException(禁用的帳號)、
// LockedAccountException(鎖定的帳號)、UnknownAccountException(錯誤的帳號)、
// ExcessiveAttemptsException(登錄失敗次數過多)、IncorrectCredentialsException (錯誤的憑證)、
// ExpiredCredentialsException(過期的憑證)等,具體請查看其繼承關係;
// 對於頁面的錯誤消息展示,最好使用如“用戶名/密碼錯誤”而不是“用戶名錯誤”/“密碼錯誤”,
// 防止一些惡意用戶非法掃描帳號庫;
try{subject.login(token);}catch (AuthenticationException e){
System.out.print("登錄失敗");
}
Assert.assertEquals(true,subject.isAuthenticated());
System.out.print("登錄成功");
// 6、最後可以調用subject.logout退出,其會自動委託給SecurityManager.logout方法退出
subject.logout();
}
}