保護敏感數據是所有企業的關鍵安全任務之一。為了確保這種保護,企業需要仔細控制誰可以訪問什麼,因此實施身份和訪問管理(IAM)機制至關重要。
然而,由於IAM 選項多種多樣,決定使用哪種服務、如何配置它以及是否需要自定義解決方案來實現安全目的可能會很困難。
在本文中,我們探討了開發身份和訪問管理服務的幾種方法之間的差異。我們展示瞭如何構建自定義本地系統和基於雲的系統的詳細示例,並將這些系統與IAM 系統的關鍵標准進行比較。
本指南對於想要探索構建IAM 系統的選項並了解技術細節和細微差別的開發領導者將很有幫助。
什麼是IAM 服務以及如何構建一項服務?根據Gartner 的說法,身份和訪問管理使正確的個人能夠在正確的時間以正確的理由訪問正確的資源。不同類型和規模的企業都努力實施IAM 實踐,通過控制用戶對關鍵信息的訪問來保護敏感信息並防止數據洩露。
為了實施這些實踐,企業使用專門的系統來提供必要的功能,例如雙因素或多因素身份驗證、用戶身份管理、用戶授權功能、權限控制等。
高效的IAM 服務應遵循零信任原則,這意味著它必須提供以下內容:
基於身份的安全性,確保系統知道登錄用戶並確認用戶的身份
基於角色的訪問權限,確保每個帳戶擁有盡可能少的權限:例如,僅具有日常工作所需的訪問權限
保護用戶的其他安全措施,例如多重身份驗證(MFA)、登錄嘗試計數、針對暴力和機器人攻擊的策略、審核日誌和備份可能性
您可以在本地部署IAM 系統、訂閱第三方供應商提供的基於雲的模型或使用混合模型。
出於本文的目的,我們決定比較構建IAM 解決方案的幾種方法:
基於作為開放集成中心(OIH) 分支的自託管服務器開發本地IAM 解決方案。
使用Auth0 配置由雲提供商管理的IAM 解決方案。
使用AWS Cognito 配置由雲提供商管理的IAM 解決方案。
但在我們開始比較開發身份和訪問管理服務的方法之前,讓我們先討論一下我們的解決方案的功能並探索其流程。
IAM 系統的關鍵功能定義未來解決方案的需求至關重要,這樣我們就可以在不同的實現準備就緒後對其進行公平的比較。
我們的目標是提供一個覆蓋1000 到5000 個用戶的IAM 系統,這是中小型企業中的實際用戶數量。
此類解決方案必須涵蓋的常見任務包括:
提供對最終用戶來說簡單的用戶註冊流程
基於密碼的身份驗證,確認用戶的真實身份
基於令牌的授權,確保用戶被授予其應有的確切級別和類型的訪問權限
創建獨特的角色和權限以及將它們分配給實體或從實體中刪除它們的能力
防止任何竊取用戶身份的企圖
訪問審查和事件響應
IAM 解決方案可能包含的其他功能包括:
安全身份驗證,例如支持MFA
通過社交網絡、Google 和Microsoft 等第三方身份提供商進行身份驗證
多租戶支持提供管理多個企業的能力
現成的UI 表單可減少從頭開始創建自定義UI 表單的時間和成本
在本文中,我們探討了創建IAM 解決方案的三種不同方法,確保必備列表中的功能,並在我們使用的平台允許的情況下嘗試實現上面列出的其他功能。
典型的IAM管理流程下圖從用戶、管理員和租戶的角度直觀地展示了基本理論IAM 管理流程:
以下是用戶與IAM 解決方案交互的方式:
用戶要么已經擁有訪問令牌,要么訪問令牌被重定向到IAM 服務進行身份驗證。
為了進行身份驗證,用戶指定其用戶名和密碼,或者選擇其他身份驗證選項,例如通過社交網絡進行身份驗證或使用授權代碼流方法。
當用戶通過身份驗證後,系統向資源服務器發出請求。
資源服務器包含一個庫,用於驗證用戶是否有權訪問資源服務器內的業務邏輯部分或向IAM 服務發出請求,後者根據其數據庫檢查用戶權限。
管理員具有以下權限:
分配給他們的一組預定義角色
使他們能夠創建新租戶空間的系統權限
租戶具有以下條件:
一組預定義的角色,允許他們僅執行與租戶相關的操作
在租戶空間內管理用戶和角色/權限的機會
定義了需求和一般程序流程後,讓我們開始實際實施。我們首先開發基於開放集成中心的自定義IAM 服務。
1. 使用開放集成中心開發定制的IAM 解決方案開放集成中心(OIH) 是一個框架,可幫助開發人員確保業務應用程序之間輕鬆進行數據交換。它由多種服務組成,包括身份和訪問管理服務。
如果您需要完全控制解決方案,使用OIH 開發IAM 服務是一個不錯的選擇。但是,這個框架不提供任何精美的UI 進行自定義,您必須手動完成所有工作。
在本例中,我們將使用本地IAM 服務器並花時間:
必要時分叉、審核和修改源代碼
修復問題並維護部署管道、備份/恢復過程等。
OIH 提供的IAM 服務的主要特點是:
支持最少的任務集,包括企業和用戶管理、身份驗證、授權和基於角色的訪問控制(RBAC)
包含最小的依賴集,依賴很少的外部服務
使用JSON Web Tokens(JWT)、OAuth 2.0和OpenId Connect等基本且眾所周知的技術
要開始開發自定義解決方案,您需要執行以下步驟:
分叉現有IAM 解決方案的源代碼
必要時修改邏輯
創建Mongo 數據庫
配置RabbitMQ代理
準備託管環境
現在,我們來討論如何使用OIH 實現我們的解決方案的基本流程。
1.1.驗證對於身份驗證,我們將描述一種不涉及外部社交聯繫的方法。要通過身份驗證,用戶應添加到至少一個企業(綁定到租戶)。否則,身份驗證流程將會失敗。
身份驗證過程分為三個主要階段:
第1 階段:用戶提供登錄名和密碼,並從api/v1/session端點檢索會話令牌。在此階段,我們不需要任何有關用戶會員資格的信息。唯一的目標是驗證該用戶並獲取可以與JWT 交換的會話令牌。
以下代碼片段演示瞭如何獲取api.js 文件中的令牌:
router.post('/session',authMiddleware.authenticate,authMiddleware.accountIsEnabled,async(req,res,next)={
if(!req.user){
//req.userwillbesetafterauthMiddleware.authenticate
returnnext({status:401,message:CONSTANTS.ERROR_CODES.NOT_LOGGED_IN});
}
constt=awaitTokenUtils.create(req.user);//idtokenwillbecreatedinalocaldatabase
req.headers.authorization='Bearer${t.token}';
res.status(200).send({token:t.token,id:t._id});
});出於可讀性目的,會話端點被分解為多個預處理程序。我們的系統將在創建ID 令牌之前調用每個預處理程序。
此時,我們最感興趣的是中間件函數,也稱為預處理程序或鉤子函數-authMiddleware.authenticate它通過與Passport 庫集成來工作,而Passport 庫又使用Mongo 數據庫進行會話存儲:
authenticate:(req,res,next)={
passport.authenticate('local',async(err,user,errorMsg)={
if(err){
returnnext(err);
}
if(errorMsg){
if(errorMsg.name==='IncorrectPasswordError'){
/*todo:increaselogintimeoutfortheuser*/
awaitAccount.updateOne({
username:req.body.username,
},{
$inc:{
'safeguard.failedLoginAttempts':1,
},
},{
timestamps:false,
});
returnnext({status:401,message:CONSTANTS.ERROR_CODES.PASSWORD_INCORRECT});
}
if(errorMsg.name==='IncorrectUsernameError'){
returnnext({status:401,message:CONSTANTS.ERROR_CODES.USER_NOT_FOUND});
}
}
if(!user){
returnnext({status:401,message:CONSTANTS.ERROR_CODES.DEFAULT});
}
req.logIn(user,async(err)={
if(err){
log.error('Failedtologinuser',err);
returnnext({status:500,message:CONSTANTS.ERROR_CODES.DEFAULT});
}
if(req.body['remember-me']){
req.session.cookie.maxAge=30*24*60*60*1000;//30days
}else{
req.session.cookie.expires=false;//expiresatendofsession
}
awaitAccount.updateOne({
username:req.body.username,
},{
$set:{
'safeguard.lastLogin':newDate(),
'safeguard.failedLoginAttempts':0,
},
},{
timestamps:false,
});
req.session.save((err)={
if(err){
log.error('Errorsavingsession',err);
returnnext(err);
}
returnnext();
});
});
})(req,res,next);
},Passport 庫依賴於基於身份驗證的策略,您可以使用以下代碼初始化該策略:
constpassport=require('passport');
constMongoStore=require('connect-mongo')(session);
constLocalStrategy=require('passport-local').Strategyconst
session=require('express-session');
/*
.
*/
constmongoSession=session({
secret:process.env.IAM_SESSION_COOKIE_SECRET,
name:process.env.IAM_SESSION_COOKIE_NAME,
store:newMongoStore({
mongooseConnection:this.mongoose.connection,
touchAfter:4*3600,
autoRemove:'native',
autoRemoveInterval:60*4,
ttl:3*24*60*60,
}),
saveUninitialized:false,
resave:false,
});
this.app.use(mongoSession);
this.app.use(passport.initialize());
this.app.use(passport.session());
//authenticationStrategyreadsauserfromthedatabaseandchecksapassword
passport.use(newLocalStrategy(authenticationStrategy()));
/*
.
*/當身份驗證中間件被觸發時,Passport 庫會調用我們的身份驗證策略。
如果一切順利,身份驗證策略將返回用戶數據,並且會話將保存在存儲中。登錄嘗試次數將重置為0。
如果出現錯誤,我們需要首先檢查密碼是否錯誤,並增加嘗試登錄失敗的次數。這是我們可以實施強力安全的地方。我們需要跟踪失敗的登錄嘗試,並提出一種策略來阻止登錄嘗試一段時間。
第2 階段:檢索到ID 令牌後,我們可以使用它來獲取用戶所屬的企業列表:
以下是檢索企業列表的代碼:
router.get('/organizations',authMiddleware.validateSession,async(req,res,next)={
try{
constaccount=awaitAccountDAO.findOne({_id:req.user.userid});
res.status(200).send({
account,
organizations:req.user.organizations,//organizationsisapartofuserrepresentationanddefinesusermembershipinorganizations
});
}catch(err){
logger.error(err);
returnnext({status:500,message:CONSTANTS.ERROR_CODES.DEFAULT});
}
});以下是validateSession 中間件如何檢查第一步中檢索到的ID 令牌:
validateAuthentication:async(req,res,next)={
letpayload=null;
letclient=null;
lettoken=null;
/**Userhasavalidcookie*/
if(req.user){
req.user=req.user.toJSON();
req.user.userid=req.user._id.toString();
returnnext();
}
//hereweneedidtokenretrievedfrom/session
if(!req.headers.authorization){
returnnext({status:401});
}
try{
constheader=req.headers.authorization.split('');
if(!header||header.length2){
log.debug('Authorizationheaderisincorrect');
returnnext({status:401,message:CONSTANTS.ERROR_CODES.INVALID_HEADER});
}
token=header[1];
payload=awaitTokenUtils.getAccountData(token);
}catch(err){
log.warn('Failedtoparsetoken',err);
returnnext({status:401,message:CONSTANTS.ERROR_CODES.SESSION_EXPIRED});
}
if(payload){
req.user=req.user||{};
returnnext();
}else{
log.error('Tokenpayloadisemptyorinvalid',{payload});
returnnext({status:401,message:CONSTANTS.ERROR_CODES.VALIDATION_ERROR});
}
}第3 階段:最後,用戶可以選擇一個企業並請求JWT 來訪問它:
以下是獲取某個企業的JWT 的代碼:
router.post('/token',authMiddleware.validateAuthentication,
async(req,res,next)={
const{organization}=req.body;
if(!organization){
returnnext({status:400,message:'Missingorganization'});
}
try{
if(awaitAccountDAO.userHasOrganization({userId:req.user.userid,tenantId:organization})){
constjwtpayload=jwtUtils.getJwtPayload(awaitAccountDAO.findOne({_id:req.user.userid}));
consttoken=awaitjwtUtils.basic.sign(jwtpayload);
req.headers.authorization='Bearer${token}';
res.status(200).send({token:token});
}
Recommended Comments