通過Citrix ADC實現過各種多因素認證,而其中最簡便的方式就是Citrix支援的Native OTP功能不依賴其他額外的元件就可以快速增強認證安全。這一年多來雙因子驗證也成為了客戶的標配需求,Native OTP是通過安裝手機APP的方式生成OTP,而大家平常使用的很多APP和網站現在都支持輸入手機號,發送簡訊驗證碼,然後通過簡訊驗證碼認證登陸的功能,因此在客戶交流過程中也有客戶對通過簡訊OTP方式實現雙因子驗證有極大興趣。
通過研究Citrix ADC強大而靈活的功能,特別是一些大家相對不熟悉的功能,包括variable,assignment,web auth,policy label等的組合並結合發簡訊服務,實現了之前demo演示的效果。
認證工作流程分析

概要工作流程
1、用戶端存取Gateway登陸頁面,會進入第一個認證因素,這裡需要使用大家最熟悉的AD認證 |
2、Gateway查詢AD以驗證用戶名密碼 |
3、AD回應認證成功消息 |
4、Citrix ADC自己生成要通過簡訊發送的6位元數位OTP |
5、Citrix ADC向簡訊寶發送HTTP API調用提交發送簡訊請求 |
6、簡訊平臺將簡訊發送到終端使用者的手機 |
7、使用者將收到的簡訊內容中的6位元數位OTP輸入到Gateway認證第二因素的使用者介面 |
8、OTP將提交給Gateway驗證 |
9、Gateway驗證提交的 OTP |
10、如果驗證成功,請求會跳轉到 StoreFront並通過第一因素AD用戶名密碼完成對StoreFront的SSO |
實現原理分析
根據大致工作流程分析,相信大家會有以下幾個問題,這也是最終如何配置實現的關鍵點。
- 如何得到用戶手機號用於發送簡訊?
為了獲取用戶手機號,這裡AD伺服器的作用不僅僅是認證。我們知道每個AD帳號都包含很多屬性,其中常用的就有mail,mobile屬性。這裡容易想到配置Gateway在AD認證的同時還要從AD使用者屬性中提取mobile屬性得到使用者的手機號碼。這也可以瞭解到整個方案實現的必要前提是現有AD帳號屬性中有用於記錄帳號所有者手機號的屬性,如果沒有則需要在AD上為帳號添加屬性,屬性可以不局限一定要用mobile,具體通過哪個屬性名獲取對應手機號和Gateway的配置有關,請參考詳細配置部分。
- 如何生成6位元數字OTP?
首先當然是由Gateway來生成,這裡會用到大家比較陌生,也是凸顯Citrix靈活性的variable(變數)和assignment(賦值)功能。從功能名稱容易想到實現的思路是創建一個變數存取OTP,在為這個變數賦值。順著這個思路,產生OTP亂數似乎比較容易,通過運算式內置的亂數random功能生成一個0-1之間的亂數,需要6位元數位OTP因此在把亂數乘以1000000即得到6位元數字OTP。當然如果要4位或者8位OTP乘以10000或者100000000即可。為了方便這裡我們把產生的結果從數位轉換為文本類型
SYS.RANDOM.MUL(1000000).TYPECAST_UNSIGNED_LONG_AT.TYPECAST_TEXT_T
接下來繼續思考通過什麼形式的變數來保存這個值,顯然不是a=123456這樣創建一個a變數賦值這麼簡單。根據工作流程分析簡訊OTP是作為第二因素進行認證的,因此在驗證簡訊OTP是否有效的過程中,顯然也要參考第一因素認證的結果,即第一因素認證的結果要和簡訊OTP產生關聯。這裡想到第一因素認證成功就會生成一個sessionid,如果能把這個sessionid和OTP關聯就可以得到想要的效果。而variable正好支援map類型的變數可以實現key-value存取的效果。通過下圖形象的說明這個實現,大白話就是2列多行的表。
sessionid | otp |
aaaaa | 342313 |
bbbbb | 112456 |
ccccc | 236789 |
通過在記憶體中生成這樣的一張表會很容易通過sessionid(key)得到對應otp(value),這張表在後續過程中還會使用。sessionid通過運算式HTTP.REQ.USER.SESSIONID非常容易獲取。
- 簡訊從哪發的以及Citrix怎麼能讓它發簡訊?
相信大家之前或多或少已經接觸過各大公有雲的簡訊服務以及各種簡訊平臺,而這些發送簡訊的服務不是傻傻的只能手動登陸進去,輸入要發送的簡訊內容點擊發送。為了實現大批量發送簡訊的任務這些服務都提供了API接口供外部調用以實現自動化靈活的簡訊發送方式。
API介面調用方式簡單,有多簡單請看下面代碼調用示例:
1. 對帳號密碼算md5
2. import hashlib
3. #發送介面調用請求
4. import requests
5.
6.
7. #簡訊平臺定義的介面調用返回值對應的結果
8. statusStr = {
9. '0': '簡訊發送成功',
10. '-1': '參數不全',
11. '-2': '伺服器空間不支持,請確認支持curl或者fsocket,聯繫您的空間商解決或者更換空間',
12. '30': '密碼錯誤',
13. '40': '帳號不存在',
14. '41': '餘額不足',
15. '42': '帳戶已過期',
16. '43': 'IP地址限制',
17. '50': '內容含有敏感詞'
18. }
19.
20. #算密碼的MD5值
21. def md5(pwd):
22. m = hashlib.md5()
23. m.update(pwd.encode("utf8"))
24. return m.hexdigest()
25.
26. #調用介面
27. def send(params):
28. smsapi = http://api.smsbao.com/sms?
29. #調用介面傳入介面url和參數並獲取調用結果返回值
30. res = requests.get(smsapi, params=params).text
31. #根據返回值獲取對應的結果,如返回0則代表簡訊發送成功
32. print(statusStr[res])
33.
34.
35. if __name__ == '__main__':
36. data = {
37. # 簡訊平臺帳號
38. 'u': 'xxxx',
39. # 簡訊平臺密碼
40. 'p': md5('xxxxx'),
41. # 要發送的簡訊內容
42. 'c': '【Workspace用戶】您的驗證碼為123456',
43. # 要發送簡訊的手機號碼
44. 'm': '18611593669'
45. }
46. #用data部分設置的帳號,簡訊內容,手機號發送簡訊
47. send(data)
現在運行這個代碼(帳號密碼部分替換為自己的)我的手機就可以收到簡訊平臺發送的簡訊內容為“【Workspace用戶】您的驗證碼為123456”的簡訊。(而你也可以多沖點錢然後迴圈執行這個代碼就成簡訊炸彈了)
由這個代碼可以聯想到假如通過Citrix ADC來實現代碼中requests模組的功能即通過http的方式發送data部分的參數到簡訊API那不就舒服了。這裡估計有老手會先想到Citrix ADC的http callout功能,沒錯它是可以主動往外發http請求但這裡不光要發請求還要判斷結果所以這裡使用web auth功能實現。這裡順便在說明上面描述的選擇簡訊的第三個原因-介面調用簡單,即介面認證和介面資料發送在一個請求返回裡就可以完成,這主要是為了配合web auth的配置,它只能配置一個請求並判斷返回。
- Citrix ADC如何同時認證AD帳號和簡訊OTP?
這裡可以對照如何生成6位元數位OTP部分理解,在生成OTP的過程中我們利用variable建立了一張key-value的表來存放OTP,表的key是sessionid,value是OTP,而sessionid是通過AD認證以後Citrix ADC生成的,我們通過查詢key就可以得到對應的OTP,即通過sessionid在表裡查到的OTP值和簡訊OTP輸入框輸入的值相等就代表用戶的2個因素都認證成功了。
配置過程
寫到這我們實現的設計邏輯以及如何通過Citrix ADC的功能模組來實現都解釋清楚了,下面進入最關心的怎麼配環節。
1.配置AD認證獲取使用者手機號

add authentication ldapAction ad1 -serverIP 172.28.7.8 -ldapBase "DC=test, DC=com" -ldapBindDn user1@test.com -ldapBindDnPassword ****** -Attribute1 mobile
這裡配置大家最熟悉的AD認證,唯一區別就是最後一個選項-Attribute1 mobile即從AD伺服器上使用者的mobile屬性讀取相應的值,這個值通過運算式AAA.USER.ATTRIBUTE(1) 就可以獲取,即手機號。

2.配置生成並存儲6位元數位OTP
選擇功能表AppExpert-Variables,添加一個map類型的變數SMS_OTP,即2列2500行的表,第一列即key的長度65位元組,第二列即value的長度6位元組。表的每一行保留300s。表的行數,key的長度可以根據實際調整
add ns variable SMS_OTP -type "map(text(65),text(6),2500)" -ifNoValue undef -expires 300

選擇功能表AppExpert-Assignment,配置assignment SMS_OTP給上一步配置的變數表賦值,參考實現分析部分內容,key賦值為AD認證成功後生成的sessionid,通過運算式HTTP.REQ.USER.SESSIONID獲取,value賦值為通過運算式生成的6位亂數。這裡還有一個關鍵是通過$SMS_OTP[HTTP.REQ.USER.SESSIONID]的方法實現通過key獲取value的值。
add ns assignment SMS_OTP -variable "$SMS_OTP[HTTP.REQ.USER.SESSIONID]" -set "SYS.RANDOM.MUL(1000000).TYPECAST_UNSIGNED_LONG_AT.TYPECAST_TEXT_T\n"

3.配置web auth調用簡訊寶API發簡訊
選擇功能表Security-AAA -Policies-Authentication-Advanced Policies-Actions-WebAuth Actions ,serverip為簡訊寶功能變數名稱解析的地址。
HTTP request expression,這裡以一個實際請求URL的例子來對比運算式理解。
1. GET /sms?u=omfg&p=f88df6d8d4701de63aec878a4ce33544&m=18588851406&c=【Workspace用戶】您的驗證碼為1234,在1分鐘內有效。HTTP/1.1
2. Accept:*/*
3. Host: api.smsbao.com
- “/sms?”部分為固定值
- “u=omfg&p=f88df6d8d4701de63aec878a4ce33544&m=18588851406&c=【雲桌面用戶】您的驗證碼為123456″為參數部分,其中u為簡訊寶用戶名-固定值,p為簡訊寶密碼MD5值-固定值,m為手機號通過運算式AAA.USER.ATTRIBUTE(1) 動態獲取,c為簡訊發送的內容,123456通過調用變數$SMS_OTP[AAA.USER.SESSIONID]獲取
- HTTP 1.1, Accept和Host頭都為HTTP標準格式中間通過回車換行分開
由此整理得到完整的介面請求格式”GET /sms?” + my_exp1 + ” HTTP/1.1″ + “\r\nAccept:*/*\r\nHost: api.smsbao.com\r\n\r\n”,my_exp1為整個請求參數部分寫成一個運算式在這裡調用看起來短一些,各部分之間用+號連接起來。因為請求內容裡有中文所以要做URL編碼,可以通過https://tool.chinaz.com/tools/urlencode.aspx實現,即%E3%80%90%E4%BA%91%E6%A1%8C%E9%9D%A2%E7%94%A8%E6%88%B7%E3%80%91%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E4%B8%BA\這堆就是”【Workspace用戶】您的驗證碼為”編碼的內容
Expression to validate the Authentication,這裡通過運算式HTTP.RES.BODY(1)獲取調用介面的返回值,如果是0就代表簡訊發送成功

1. add authentication webAuthAction duanxinbao1 -serverIP 120.27.83.216 -serverPort 80 -fullReqExpr q{"GET /sms?" + my_exp1 + " HTTP/1.1" + "\r\nAccept:*/*\r\nHost: api.smsbao.com\r\n\r\n"} -scheme http -successRule "HTTP.RES.BODY(1).EQ(\"0\")" 2. add policy expression my_exp1 "\"u=omfg&p=f88df6d8d4701de63aec878a4ce33544&m=\" + AAA.USER.ATTRIBUTE(1) + \"&c=%E3%80%90%E4%BA%91%E6%A1%8C%E9%9D%A2%E7%94%A8%E6%88%B7%E3%80%91%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E4%B8%BA\" + $SMS_OTP[AAA.USER.SESSIONID] "
4.配置authentication policy lable
前面每個單獨步驟都配置完成了,這裡通過authentication policy label將AD認證,生成OTP,發送OTP,驗證OTP這幾步串聯起來。
首先在AAA Vserver上綁定AD認證策略,這個配置是大家平常使用最多的了,注意區別是AD認證完了接下來(next)要進入生成OTP的步驟,因此next factor就是generate_otp策略

選擇功能表Security-AAA -Policies-Authentication-Advanced Policies-authentication policy labels功能表,將生成OTP,
發送OTP以及驗證OTP組合起來。最終效果如圖

Generate_OTP我們要綁定兩個策略,第一個策略的action配置為assignment-SMS_OTP,第二個策略action為不認證目的只是為了將next factor指向下一步發送OTP

1. dd authentication Policy Generate_OTP -rule true -action SMS_OTP
2. add authentication policylabel Generate_OTP -loginSchema LSCHEMA_INT
3. bind authentication policylabel Generate_OTP -policyName Generate_OTP -priority 90 -gotoPriorityExpression NEXT
4. bind authentication policylabel Generate_OTP -policyName Cascate_NoAuth -priority 100 -gotoPriorityExpression NEXT -nextFactor Send_OTP
Send_OTP只需要綁定一個策略,將action配置為web auth-duanxinbao1,next factor指向下一步驗證OTP

1. add authentication Policy Send_OTP -rule true -action duanxinbao1
2. add authentication policylabel Send_OTP -loginSchema LSCHEMA_INT
3. bind authentication policylabel Send_OTP -policyName Send_OTP -priority 100 -gotoPriorityExpression NEXT -nextFactor Validate_OTP
Validate_OTP只需要綁定一個策略,這裡和前兩步策略綁定有區別在於這裡是用運算式來對OTP進行驗證的,即實現原理分析部分說明的通過sessionid在表裡查到的OTP值($SMS_OTP[aaa.user.sessionid])和簡訊OTP輸入框輸入的值(aaa.login.password)相等就代表用戶的2個因素都認證成功了。這步注意還要配置輸入簡訊OTP的驗證框,即login schema,這裡選擇Single_Password這個login schema,下一節會介紹如何配置。到這步認證過程就結束了因此goto expression選擇END

1. add authentication Policy OTP_Verify -rule "AAA.LOGIN.PASSWORD.EQ($SMS_OTP[aaa.user.sessionid])" -action NO_AUTHN
2. bind authentication policylabel Validate_OTP -policyName OTP_Verify -priority 100 -gotoPriorityExpression END
5.配置login schemas
這裡有需要配置兩個login schema,一個是第一步認證輸入AD用戶名密碼的,還有就是第二步認證輸入簡訊OTP的。第一個綁定到AAA vserver即可,第二個綁定到上一節介紹的policy label。現在login schema的文字提示部分已經支援中文。這裡還有一個講究我們要用AD作為SSO憑據到storefront,因此在這裡配置Enable Single Sign On Credentials選項,而第二步的OTP只作為認證因素,和storefront SSO無關因此不需要配置。



1. add authentication loginSchema Single_Password -authenticationSchema "/nsconfig/loginschema/LoginSchema/OnlyPassword.xml"
2. add authentication loginSchema First_factor_layout -authenticationSchema "/nsconfig/loginschema/LoginSchema/SingleAuth.xml" -SSOCredentials YES
3. add authentication loginSchemaPolicy First_factor_schema -rule true -action First_factor_layout
4. bind authentication vserver SMS_OTP_AAA -policy First_factor_schema -priority 100 -gotoPriorityExpression END
6. 配置AAA vserver和authentication profile
通過AAA vserver將authentication policy和login schema關聯到一起,並配置authentication profile關聯AAA vserver供gateway vserver調用。
1. add authentication vserver SMS_OTP_AAA SSL 0.0.0.0
2. bind authentication vserver SMS_OTP_AAA -policy First_factor_schema -priority 100 -gotoPriorityExpression END
3. bind authentication vserver SMS_OTP_AAA -policy First-Login -priority 100 -nextFactor Generate_OTP -gotoPriorityExpression NEXT
4. add authentication authnProfile SMS_OTP_Profile -authnVsName SMS_OTP_AAA
5. add vpn vserver _XD_192.168.26.61_443 SSL 192.168.26.61 443 -Listenpolicy NONE -tcpProfileName nstcp_default_XA_XD_profile -deploymentType ICA_STOREFRONT -authnProfile SMS_OTP_Profile
有個經典的繞口令,負載均衡為啥不叫負載均衡要叫應用交付?還有經常說的靈活性,可擴展性到底如何體現,我想這篇實戰中很多的點正好體現了Citrix ADC(NetScaler)的靈活性,可擴展性以及為什麼不僅僅是負載均衡。
發表迴響