1、登錄認證
1、Session-Cookie認證
基於Session-Cookie
機制的認證是比較原始的一種認證方式,由於HTTP
協議是純文本,無狀態的傳輸協議,那么在一些需要記錄狀態的場景就很麻煩,如淘寶的購物車,不同用戶登錄后看到的購物車數據是不一致的;所以需要一種機制能讓服務端知道請求的客戶端是誰。這樣Cookie就應運而生,在客戶端登錄成功后,服務端返回的響應報文頭中會帶上一個set-cookie
,客戶端瀏覽器判斷拿到這個請求頭后會放在本地,每次訪問cookie中指定的路徑時都會帶上到請求頭中。
1、缺點
基於Session-Cookie
會帶來一系列的問題。比如:
- 服務端的
Session
都保存在內存中,登錄用戶過多后會對服務端造成較大的壓力 - 可能會引起
CSRF
攻擊 - 分布式系統中無法進行登錄認證
2、解決方案
為解決Session
存儲在服務端導致的性能瓶頸的痛點,可以將session
信息放入到緩存或者是數據庫中進行存儲,服務端在校驗登錄進來的信息時直接從緩存或者數據庫中獲取對應的信息進行比對,業內一般將Session
信息放入到Redis中進行保存。
SpringSession
1 引入pom
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
2 application.yaml配置
spring:
redis:
database: 1
host: localhost
pool:
max-active: 20
3 開啟@EnableRedisHttpSession
SpringSession原理
先從@EnableRedisHttpSession
注解類分析開始
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RedisHttpSessionConfiguration.class})
@Configuration
public @interface EnableRedisHttpSession {
int maxInactiveIntervalInSeconds() default 1800;
String redisNamespace() default "spring:session";
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
String cleanupCron() default "0 * * * * *";
}
可以看到該注解用到了Spring
的Import
來加載需要的bean
在RedisHttpSessionConfiguration
配置類中主要的功能有以下:
@Bean
public RedisOperationsSessionRepository sessionRepository() {
RedisTemplate<Object, Object> redisTemplate = this.createRedisTemplate();
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = this.resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
}
創建一個RedisOperationsSessionRepository
對象提供給SessionRepositoryFilter
操作session的工具。
核心就在SessionRepositoryFilter
過濾器中:
@Order(-2147483598)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
// 省略不必要的代碼
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);
SessionRepositoryFilter.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
}
}
可以看到該過濾器的優先級非常的高,那么請求進入服務器第一個就會來到該過濾器。該過濾器將HttpServletRequest
和HttpServletResponse
進行包裝然后開始執行下一個過濾器鏈路,其實包裝的本質也就是將HttpServletRequest
的getSession進行了加強,改為通過RedisOperationsSessionRepository
從redis
中獲取
2、Token認證
由於Session-Cookie
的諸多不便的缺點,現在大部分公司采用的技術方案是Token
。Token
的機制和Cookie
其實差不多,本質都是用來解決HTTP
協議的無狀態痛點。此外Token認證的方式還能解決CSRF
攻擊的問題。
1、認證token
一般是將用戶id和用戶名稱和進行雙向加密后返回給客戶端,客戶端再每次請求時都需要再請求頭中帶上Token
。服務端接收到token
后將token
發往SSO服務進行校驗,校驗通過后會獲得登錄用戶的權限和基本信息,登錄失敗后將拒絕訪問。
此方式和Session-Cookie
的區別在於客戶端的存儲方式,Cookie
是直接存儲在瀏覽器中,訪問的時候直接帶過去,在web場景中這樣是可以的,但是一旦涉及到移動端此方式就會有問題,所以對於跨平台的接口還是采用Token
的方式兼容性最好
2、JWT
JWT全名為:json web token。WT是由三段信息構成的,將這三段信息文本用
.
鏈接一起就構成了Jwt字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT構成:
- 頭部
- 載荷
- 簽證
頭部信息header:
加密的算法
{ 'typ': 'JWT', 'alg': 'HS256' }
載荷payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
載荷中有以下幾種數據:
保留數據:
iss(Issuser):代表這個JWT的簽發主體; sub(Subject):代表這個JWT的主體,即它的所有人; aud(Audience):代表這個JWT的接收對象; exp(Expiration time):是一個時間戳,代表這個JWT的過期時間; nbf(Not Before):是一個時間戳,代表這個JWT生效的開始時間,意味着在這個時間之前驗證JWT是會失敗的; iat(Issued at):是一個時間戳,代表這個JWT的簽發時間; jti(JWT ID):是JWT的唯一標識。
私有數據:
登錄人員的基本信息
簽章
Signature
Jwt
的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
這個部分需要base64加密后的header和base64加密后的payload使用
.
連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret
組合加密,然后就構成了jt的第三部分var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret');
最后
JWT
總體=base64UrlEncode(header) + '.' + base64UrlEncode(payload) + '.' + signature;
JWT
和普通的Token
區別在於JWT
的載荷是存放在客戶端中的,一般的Token
都是客戶端存放一個GUID
通過GUID
去服務端中拿到對應的登錄人員信息,JWT
就可以將這一步直接省略掉。這樣做的好處的可以減少服務端計算的壓力。
缺點:
- 服務端無法注銷,由於
JWT
的有效性和服務端沒有依賴關系,所以服務端也無法使JWT
失效。 JWT
無法被續簽。
解決方案
針對JWT
無法被服務端注銷問題:
- 將
token
存入數據庫中,每次拿到token
后和數據庫中的token
進行比對,如果沒有找到則說明token
已經失效,但是這樣做就散失了JWT
的優勢,違背了JWT
無狀態原則 - 使用內存維護一個黑名單列表,需要失效則將
token
存放到黑名單中,每次認證需要先判斷是不是已經被拉黑的token
- 修改
JWT
的Secret
,不推薦。會將所有之前簽發的token
全部失效 - 保持令牌的短有效期,需要經常去簽發新的
token
針對JWT
無法續簽問題:
- 服務端發現
Token
快過期就簽發一個新的token
給客戶端。客戶端判斷接口是否發送新的token
,如果是則用新token來替換舊的 - 將
token
有效期設置到半夜,保證大部分用戶白天都能登錄,適用於安全性要求不高的系統 - 雙
token
機制:accessToken
和refreshToken
,accessToken
有效期較短如半個小時,refreshToken
有效期較長如一天。accessToken
用來獲取受限的服務資源,當accessToken
失效時,通過refreshToken
來獲取新的accessToken
3、OAuth2協議
1、名詞說明
Third-party application
:第三方應用程序,本文中又稱"客戶端"(client),比如打開知乎,使用第三方登錄,選擇qq登錄,這時候知乎就是客戶端。HTTP service
:HTTP服務提供商,本文中簡稱"服務提供商",即上例的qq。Resource Owner
:資源所有者,本文中又稱"用戶"(user),即登錄用戶。User Agent
:用戶代理,本文中就是指瀏覽器。Authorization server
:認證服務器,即服務提供商專門用來處理認證的服務器。
一般流行的使用都是OAuth2的授權碼模式,授權碼模式功能最完整,流程最嚴密。
2、授權碼模式流程
- 用戶訪問客戶端,客戶端將頁面導向認證服務器認證頁面,用戶點擊授權,認證訪問會將頁面導向到客戶端事先指定的重定向URL,同時附上一個授權碼
- 客戶端收到驗證碼,向認證服務申請token:
/oauth/token?response_type=code&client_id=test&redirect_uri=重定向頁面鏈接
- 認證服務核對了授權碼和重定向地址無誤后給客戶端發送兩個令牌:
accessToken
和refreshToken
,refreshToken
擁有較長的過期時間,但accessToken
失效后,通過refreshToken
進行刷新。
2、RBAC模型
1.3 - What ANSI RBAC is — Apache Directory
權限系統通常基於
RBAC
(Role-Based Access Control)的思想設計,擁有4個關鍵元素:用戶 – 角色 – 權限 – 資源。
- 資源
被安全管理的對象(
Resources
頁面、菜單、按鈕、訂單等)
- 權限
訪問和操作資源的許可(
Permit
刪除、編輯、審批等)
- 角色
我們通過業務流程確定一個角色,實際是確定角色並角色具備的那些權限的過程,角色所以是權限的集合,是眾多權限顆粒組成;
我們通過把權限給這個角色,再把角色給用戶,從而實現用戶的權限,因此它承擔了一個橋梁的作用。
引入角色這個概念,可以幫助我們靈活的擴展,使一個用戶可以具備多種角色。
- 用戶
系統實際的操作員(
User
)【用戶(
user
:誰)】被賦予【角色(role
:具有1-n個權限)】,通過角色關聯的【權限(permit
:許可)】去訪問/操作【資源(resource
)】
RBAC
解決了什么痛點?
在傳統模型中無角色概念,直接對用戶進行授權,這樣會導致一些問題:
- 配置權限很麻煩
- 無法快速配置類似角色的權限
- 用戶多身份下的角色配置很麻煩
在RBAC模型中一共分為四種:RBAC0
,RBAC1
,RBAC2
,RBAC3
,其中,RBAC1
和RBAC2
是基於RBCA0
升級衍生出來的,而RBAC3
則是融合了RBAC1
和RBAC2的優點
創造的新的模型,一般來說RBAC3
是夠用的。
1、RBAC0 基本模型
RBAC0定義了能構成RBAC控制系統的最小的元素集合,RBAC0由四部分構成:
- 用戶
- 角色
- 權限
- 會話
當一個用戶被指定角色時,該用戶就擁有了此角色的所有權限。RBAC0是最基礎的權限模型,權限設計時將權限賦予給角色,而不是用戶,一個用戶可以擁有若干角色,從而使用戶可以獲得更廣泛的權限。就此構造成“用戶- 角色- 權限”的授權模型。在這種模型中,用戶與角色之間,角色與權限之間,一般者是多對多的關系。角色和權限綁定,用戶被賦予相應的角色,通過多對多的關系來實現授權和授權的快速變更,從而控制用戶對系統的功能使用和數據訪問權限。
2、RBAC1 權限繼承
RBAC1在RBAC0的基礎上增加權限繼承的改進,為什么要加入權限繼承呢?
設想一下這個場景,公司招了一位管理人員進入,那么管理人員的權限就得是下面被管理人員的權限的超集,如果被管理人員的角色和鏈路很長的話,那么這個角色需要添加很多的權限,非常的麻煩。但是引入了權限繼承體系就會變得很簡單,管理人員的角色只需要繼承所有他下面一級角色就可以了。
角色間的繼承關系可分為一般繼承(General
)和受限繼承(Limited
)。
- 一般繼承關系:
要求角色繼承關系是一個絕對偏序關系(A角色繼承於B角色,那么B必須是A權限的一個子集,不可以冗余多余的權限),允許角色間的多繼承。
- 受限繼承關系:
進一步要求角色繼承關系是一個樹結構,實現角色間的單繼承。受限繼承則增強了職責關系的分離
3、RBAC2 權限約束
他是RBAC
的約束模型,RBAC3
也是在RBAC0
的基礎上構建的,在RBAC0
的基礎上增加了約束的概念,主要引入了靜態職責分離SSD(Static Separation of Duty)和動態職責分離DSD(Dynamic Separation of Duty)。
- 靜態職責分離SSD
在設置用戶角色權限的時候就應該判斷,如果有沖突發生在設置時候就應該拒絕。靜態職責分離有以下幾種
- 角色互斥:用戶不能同時擁有兩個互斥的角色,例如會計和審計就不能由同一個用戶擁有
- 基數互斥:一個用戶能夠擁有的角色是有限的,一個角色擁有的權限也是有限的,如:某個角色是為CEO准備的,那就不能有多個
- 先決條件角色:用戶想要獲得較高的權限,受限需要獲得低一級的權限,如:有副經理的權限才能有總經理的權限
- 動態職責分離DSD
在角色分配時可以將沖突的角色賦予給同一個用戶,但是在用戶使用系統時,一次會話中不能同時激活兩個角色。
4、RBAC3 綜合權限模型
也就是最全面級的權限管理,它是基於RBAC0的基礎上,將RBAC1和RBAC2進行整合了,最前面,也最復雜的
5、表結構的設計
RBAC
最簡單的表結構設計如下:
一個用戶擁有若干角色,每一個角色擁有若干權限。這樣,就構造成“用戶-角色-權限”的授權模型。在這種模型中,用戶與角色之間,角色與權限之間,一般者是多對多的關系
1、用戶組和角色組
當用戶量非常多的時候,需要用系統逐一給用戶授權是一件很繁瑣的事情,就需要使用用戶組,除了可以給用戶授權意外還可以給用戶組進行授權,然后將用戶加入到對應的用戶組中,這樣用戶擁有的權限就等於用戶組的權限+用戶個人角色的權限之和。
角色組不參與權限控制,當角色量較多時,可以通過樹狀圖來展示角色,這樣方便用戶去使用。
2、資源細分
接下來可以再進一步對權限進行拆分,如對於上傳文件的操作,菜單的訪問,頁面上的按鈕都屬於權限控制的范圍,在設計上將功能操作分為一類,將文件,菜單,頁面內容分成資源一類,這樣可以把操作和資源進行管理,粒度更細。
全局:
3、權限繼承
如果需要對權限要求更高還可以變成RBAC3
型的權限設計,即加上權限繼承和權限約束。采用受限繼承式的方式完成:
只需要在角色表中添加一個父角色ID就可以,當用戶登錄進入系統時,會通過用戶ID找到對應的角色集合,對集合進行一次遍歷:遞歸找到該角色對應父類的權限並加入集合中。再對所有角色進行遍歷完成后會得到一個用戶所有的權限。這樣受限繼承式的權限繼承就完成了。
如果需要使用普通繼承式,則需要再新建一個表:
這樣的處理會簡單一點,當獲取到用戶的角色集合時,遍歷獲取每個角色對應的父角色ID對應的權限,然后放入到權限集合中。
6、其他幾種權限模型
1、ACL模型
ACL稱之為權限控制列表,規定資源可以被哪些主體進行哪些操作,是ACL模型的一種靈活實現
場景:部門隔離 適用資源:客戶頁面、人事頁面
在ACL
權限模型下,權限管理是圍繞資源來設定的。我們可以對不同部門的頁面設定可以訪問的用戶。配置形式如下:
ACL配置表
資源: 客戶頁面
主體: 銷售部(組)
操作:增刪改查
主體: 王總(用戶)
操作: 增刪改查
資源: 人事頁面
主體: 王總(組)
操作: 增刪改查
注:主體可以是用戶,也可以是組。
在維護性上,一般在粗粒度和相對靜態的情況下,比較容易維護。
在細粒度情況下,比如將不同的客戶視為不同的資源,1000個客戶就需要配置1000張ACL表。如果1000個客戶的權限配置是有規律的,那么就要對每種資源做相同的操作;如果權限配置是無規律的,那么ACL不妨也是一種恰當的解決方案。
在動態情況下,權限經常變動,每添加一名員工,都需要配置所有他需要訪問的資源,這在頻繁變動的大型系統里,也是很難維護的。
2、DAC模型
DAC
稱之為自主訪問控制(DAC
: Discretionary Access Control
)
系統會識別用戶,然后根據被操作對象(Subject
)的權限控制列表(ACL: Access Control List
)或者權限控制矩陣(ACL: Access Control Matrix
)的信息來決定用戶的是否能對其進行哪些操作,例如讀取或修改。DAC
是ACL
的一種實現,強調靈活性。純粹的ACL,權限由中心管理員統一分配,缺乏靈活性。為了加強靈活性,在ACL的基礎上,DAC模型將授權的權力下放,允許擁有權限的用戶,可以自主地將權限授予其他用戶。
而擁有對象權限的用戶,又可以將該對象的權限分配給其他用戶,所以稱之為自主控制。在文件系統的設計中常用此模式,如微軟的NTFS。
DAC
的缺點在於權限控制較於分散,不方便去管理,且也是用戶直接和權限掛鈎。適用於權限結構簡單,用戶類型和數量不多的場景。
3、MAC模型
MAC稱之為強制訪問控制(
MAC: Mandatory Access Control
)是ACL模型的另一種實現,主要體現在了安全性訪問權限有兩個規則判斷
- 規定資源可以被哪些類別的主體進行哪些操作
- 規定主體可以對哪些等級的資源進行哪些操作
1和2同時滿足才可以通過權限認證
場景:保密系統 適用資源:機密檔案
MAC
是ACL
的另一種實現,強調安全性。MAC
會在系統中,對資源與主體,都划分類別與等級。比如,等級分為:秘密級、機密級、絕密級;類別分為:軍事人員、財務人員、行政人員。比如,一份機密級的財務檔案,可以確保只有主體的等級是機密級,且是財務人員才能訪問。如果是機密級的行政人員就無法訪問。MAC
的優勢就是實現資源與主體的雙重驗證,確保資源的交叉隔離,提高安全性。
資源配置表
資源: 財務文檔
主體: 財務人員
等級:機密級
操作:查看
主體配置表
主體: 李女士
類別: 財務人員
等級:機密級
4、ABAC模型
基於屬性的權限驗證(
ABAC: Attribute-Based Access Control
) 規定哪些屬性的主體可以對哪些屬性的資源在哪些屬性的情況下進行哪些操作,
場景:防火牆 適用資源:端口訪問
ABAC中主要的一些參數:
- 主體屬性,主體相關信息,如姓名,性別,職位,年齡等等
- 資源屬性,資源相關的屬性,如資源創建時間,創建位置,保密等級等等
- 情況屬性,指的是客觀情況的屬性,如當前時間,當前位置,當前場景
- 操作,增刪改查
設定一個權限,就是定義一條含有四類屬性信息的策略(Policy
)。
例如:
策略表
效果:允許
操作:流入
主體:來自上海IP的客戶端
資源:所有以33開頭的端口(如3306)
情況:在北京時間 9:00~18:00
效果:禁止
操作:流出
主體:任何
資源:任何
情況:任何
一個請求來到系統中,會逐條的匹配策略。匹配的規則有很多種:
- 如果沒有匹配到,則返回默認策略,拒絕或者接受
- 匹配到多個策略
- 必須完全匹配接受
- 有任意一個接受則接受
3、OpenAPI安全
接口需要開放給互聯網調用需要做好安全控制,不然服務會被惡意攻擊拖垮,如何做好OpenAPI,我認為有以下幾點需要:
- 安全認證
- 開發者門戶,用於開發應用的注冊和操作
- 接口熔斷降級處理,一般來說通過Hytrix
- 日志記錄,在輸入和輸出要進行日志記錄
首先客戶端調用開放接口之前需要去開發者門戶注冊一個應用賬戶,應用會得到自身的AppKey
和AppSecret
,其中AppKey
是相當於用戶名,AppSecret
相當於密碼。OpenApi調用的過程如下:
- 先通過
HTTPS
協議登錄獲取Access_Token
(以下稱之為Token
)和Refresh_Token
Token
認證指客戶端請求黑名單接口時,認證中心基於Token
生成簽名
Token表結構:
接口請求:
請求參數中至少有以下幾個屬性:
- 方法名,指定調用哪個接口
- 業務參數,接口的請求參數用JSON字符串
- token值,登錄后獲取到的token
- 簽名,將AppKey,APPSecret,Token和時間戳,和業務參數進行一次哈希算法得到簽名值,防止請求參數被篡改
- 時間戳,服務端用來判斷請求時間是否有效作用
服務端簽名驗證的具體流程: