JWT的權限控制與Shiro入門

橡皮筋兒 2021-09-19 18:51:27 阅读数:514

jwt 控制 shiro

一、前端權限控制

1.1介紹

在vue工程中,需要根據登錄用戶所擁有的權限信息動態的加載菜單列錶(路由列錶)

  1. 登錄成功後獲取用戶信息,包含權限列錶(菜單權限,按鈕權限等)
  2. 根據用戶的權限,去動態的渲染頁面(根據路由名稱和權限標識比較)
  3. 頁面按鈕權限通過自定義方法控制可見性

1.2具體

  1. 此時需要准備所有使用該系統的用戶的等級:
  • saas管理員----擁有所有權限
  • 企業管理員----創建租戶企業的權限
  • 普通用戶-----被分配的權限
  1. 此時前端訪問登錄界面後,就開始渲染頁面,使用了路由
  2. 路由首先把公共的組件路由出來,然後調用前置構字函數(beforeEach),獲取用戶的信息存儲起來。
  3. 最後判斷當前的模塊路由是否具有訪問權限,來决定是否渲染此路由。

二、有狀態服務和無狀態服務

對服務器程序來說,究竟是有狀態服務,還是無狀態服務,其判斷依據——兩個來自相同發起者的請求在服務器端是否具備上下文關系。

2.1無狀態服務

無狀態服務對於客戶端的單次請求,不會依賴其他請求的數據。即:處理請求的所需信息全部包含在該請求裏。

如:cookie保存token的方式傳輸數據,對於服務端來講,每次只是驗證token是否合法,是否是我這個服務端頒發出的內容,從而辨別是否有權限,而不會保存用戶的信息。

2.2有狀態服務

有些數據會被記錄在服務端,先後的請求是有關聯的

當客戶端登錄後,服務端會頒發一個sessionId,此時在服務端就存儲了該sessionId對應的session信息。客戶端一般把返回的sessionId存儲在cookie中。從而將http的無狀態服務變相轉換為有狀態服務。

三、基於JWT的API鑒權

3.1JWT(JSON Web Token)

3.1.1 介紹

JSON Web Token (JWT)是一個開放標准(RFC 7519),它定義了一種緊凑的、自包含的方式,用於作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。

JWT是由三段信息構成的,將這三段信息文本用.鏈接一起就構成了Jwt字符串。就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

3.1.2JWT的構成

第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).

1.header

jwt的頭部承載兩部分信息:

  • 聲明類型,這裏是jwt
  • 聲明加密的算法 通常直接使用 HMAC SHA256

完整的頭部就像下面這樣的JSON:

{
'typ': 'JWT',
'alg': 'HS256'
}

然後將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.

2.playload

載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分

  • 標准中注册的聲明
  • 公共的聲明
  • 私有的聲明

定義一個payload:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

然後將其進行base64加密,得到Jwt的第二部分。

3.signature

jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:

  • header (base64後的)
  • payload (base64後的)
  • secret

這個部分需要base64加密後的header和base64加密後的payload使用.連接組成的字符串,然後通過header中聲明的加密方式進行加鹽secret組合加密,然後就構成了jwt的第三部分。

注意

secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。

3.1.3JWT與Session的差异

  1. Session是在服務器端的,而JWT是在客戶端的。
  2. Session方式存儲用戶信息的最大問題在於要占用大量服務器內存,增加服務器的開銷。而JWT方式將用戶狀態分散到了客戶端中,可以明顯减輕服務端的內存壓力。
  3. Session的狀態是存儲在服務器端,客戶端只有session id;而Token的狀態是存儲在客戶端。

3.1.4 工作流程

-每一次請求都需要token -Token應該放在請求header中 -我們還需要將服務器設置為接受來自所有域的請求,用Access-Control-Allow-Origin: *

3.1.5 用Token的好處

  • 無狀態和可擴展性:Tokens存儲在客戶端。完全無狀態,可擴展。我們的負載均衡器可以將用戶傳遞到任意服務器,因為在任何地方都沒有狀態或會話信息。
  • 安全:Token不是Cookie。(The token, not a cookie.)每次請求的時候Token都會被發送。而且,由於沒有Cookie被發送,還有助於防止CSRF攻擊。
    CSRF(Cross-site request forgery),中文名稱:跨站請求偽造。CSRF攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。
  • token在一段時間以後會過期,這個時候用戶需要重新登錄。這有助於我們保持安全。

在spring攔截器中,進行攔截

四、Shiro安全框架

4.1介紹

shiro 是一個功能强大和易於使用的Java安全框架,為開發人員提供一個直觀而全面的解决方案的認證,授權,加密,會話管理。

4.2 作用

  • Authentication:驗證用戶核實身份
  • Authorization:對用戶進行訪問控制:如判斷用戶是否被允許做某件事
  • SessionManager:會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;
  • Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲

4.3 特點

Web Support:Web支持,可以非常容易的集成到Web環境;

Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;

Concurrency:shiro支持多線程應用的並發驗證,即如在一個線程中開啟另一個線程,能把權限自動傳播過去;

Testing:提供測試支持;

Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;

Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了。

SSO:單點登錄功能

4.4 快速入門

1.導入依賴

<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

2.創建配置文件

[users]
#模擬從數據庫查詢用戶
#數據格式: 用戶名=密碼
zhangsan=123456
lisi=1234

3.測試login

 /**
* 測試用戶認證
* 認證: 用戶登錄
* 1.通過配置文件創建SecurityManagerFactory
* 2.通過工廠獲取securityManager
* 3.將securityManager綁定到當前運行環境
* 4.從當前運行環境中構造subject
* 5.構造shiro登錄的數據
* 6.登錄
*/
@Test
public void testLogin(){
//1.獲取工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-1.ini");
//2.獲取安全管理
SecurityManager securityManager = factory.getInstance();
//3.綁定
SecurityUtils.setSecurityManager(securityManager);
//4.構造subject
Subject subject = SecurityUtils.getSubject();
//5.構造數據
String username = "zhangsan";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//6,登錄
subject.login(token);
//判斷是否登錄成功
System.out.println(subject.isAuthenticated());
System.out.println(subject.getPrincipal());
}

4.測試用戶權限

[users]
#數據格式: 用戶名=密碼,角色名列錶
zhangsan=123456,role1,role2
lisi=1234,role2
[roles]
#角色
#角色名=權限列錶
role1=user:save,user:update
role2=user:find
/**
* 測試用戶權限
*/
@Test
public void testAble(){
//1.獲取工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-2.ini");
//2.獲取安全管理
SecurityManager securityManager = factory.getInstance();
//3.綁定
SecurityUtils.setSecurityManager(securityManager);
//4.構造subject
Subject subject = SecurityUtils.getSubject();
//5.構造數據
String username = "lisi";
String password = "1234";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//6,登錄
subject.login(token);
//登錄成功
if (subject.isAuthenticated()){
//查看是否有role1角色
System.out.println(subject.hasRole("role2"));
//是否具有權限
System.out.println(subject.isPermitted("user:find"));
}
}
//結果:true
// false

4.5使用

1.導入maven坐標

<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

2.創建ini配置文件

其中指定編寫的realm域的比特置,並且將realm綁定到SecurityManager

[main]
#自定寫的realm域,全包名
permReam=gyb.shiro.PermissionRealm
#注册realm到SecurityManager
securityManager.realms=$permReam

3.編寫PermissionRealm類,繼承AuthorizingRealm,重寫方法

package gyb.shiro;
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 java.util.ArrayList;
import java.util.List;
/**
* @Author: 郜宇博
* @Date: 2021/9/19 16:30
*/
public class PermissionRealm extends AuthorizingRealm {
/**
* 一般重寫setName方法
*/
public void setName(){
super.setName("permissionRealm");
}
/**
* 重寫抽象doGetAuthorizationInfo:授權(獲取到用戶的授權數據)
* 目的:根據認證數據獲取用戶的權限信息
* principals:包含已認證安全數據
* AuthorizationInfo 授權數據
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("執行授權方法");
//1.獲取安全數據
//2.通過安全數據內的username查詢數據庫,獲取到權限和角色
//模擬查數據庫的內容
List<String> perms = new ArrayList<String>();
perms.add("user:save");
perms.add("user:update");
List<String> roles = new ArrayList<String>();
roles.add("role1");
roles.add("role2");
//3.構造返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//4.設置權限、角色集合
info.addStringPermissions(perms);
info.addRoles(roles);
return info;
}
/**
* doGetAuthenticationInfo :認證(根據用戶名密碼登錄,將用戶數據(安全數據)保存)
* 目的:比較用戶名和密碼是否和數據庫中的一致,並將安全數據存入到shiro中進行保管
* AuthenticationToken:登錄時構造的token
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行認證方法");
//1.構造upToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.獲取輸入的用戶名密碼
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
//3.與數據庫比較
if("123456".equals(password)){
//一致,向shiro存入安全數據
//三個參數:1.安全數據,2.密碼,3,當前realm域名稱
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}else {
throw new RuntimeException("用戶名或密碼錯誤");
}
}
}

4.使用

/**
* 通過認證授權方法進行判斷
*/
@Test
public void testShiro(){
//1.獲取工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-3.ini");
//2.獲取安全管理
SecurityManager securityManager = factory.getInstance();
//3.綁定
SecurityUtils.setSecurityManager(securityManager);
//4.構造subject
Subject subject = SecurityUtils.getSubject();
//5.構造數據
String username = "zhangsan";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//6,執行認證方法(登錄),此時自動找到配置的Shiro類(在ini文件中),然後執行認證方法
subject.login(token);
//7.執行授權方法
System.out.println(subject.hasRole("role1"));
System.out.println(subject.isPermitted("user:save"));
}

4.6工作流程

1.認證

  1. subject調用login方法,傳遞token,會自動委任給SecurityManager
  2. SecurityManager中使用認證器
  3. 認證器找到了所有的realm域,此時就找到了自己寫好的。
  4. 然後就可以執行自己實現的realm域

2.授權

首先調用isPermitted或hasRole方法,然後會委任給SecurityManager。

SecurityManager中使用授權器

版权声明:本文为[橡皮筋兒]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210919185126707R.html