近日公司有個需求,需要對使用vpn的用戶進行二次認證,要求使用動態碼進行驗證,我再網上搜了很多資料都沒有找到合適的解決方案。
使用第三方設備吧,費用太貴,不是很值得。 正好博客園有位大佬之前趟過坑,文章給了我思路。他的博文地址是:
https://www.cnblogs.com/luckpiky/p/11441916.html
廢話不多說,上我修改過的代碼。
環境說明:
py3.8 數據庫mysql (存radius第一次認證的密碼),安裝pyrad插件
修改過的
1 # -*- coding: UTF-8 -*- 2 2 from pyrad import * 3 3 import socket 4 4 import pyrad.host 5 5 import random 6 6 import pymysql 7 7 8 8 BUFSIZE = 1024 9 9 KEY = b"test123456789" 10 10 CHALLENGE = "666" #挑戰碼初始為666 11 11 12 12 class RadiusServer(pyrad.host.Host): 13 13 def __init__(self): 14 14 dict = pyrad.dictionary.Dictionary("dictionary") #從通用的字典使用 15 15 pyrad.host.Host.__init__(self, dict=dict) 16 16 17 17 def get_challenge(self): #產生一個4字節的隨機挑戰碼 18 18 challenge = "" #初始化挑戰碼 19 19 challenge = challenge + str(chr(random.randint(65,90))) 20 20 challenge = challenge + str(chr(random.randint(65,90))) 21 21 challenge = challenge + str(chr(random.randint(65,90))) 22 22 challenge = challenge + str(chr(random.randint(65,90))) 23 23 return challenge 24 24 def check_pass(self, radpkt): 25 25 global CHALLENGE 26 26 print("檢查用戶名稱") 27 27 conn=pymysql.connect( 28 28 host='x.x.x.x', #數據庫的ip地址 29 29 port=3306, #數據庫端口 30 30 user='root', #數據庫用戶名 31 31 password='12345678',#數據庫密碼 32 32 db='test', #數據庫表名 33 33 charset='utf8' #數據庫編碼 34 34 ) 35 35 # 拿到游標 36 36 cursor=conn.cursor() 37 37 user=radpkt["User-Name"][0] 38 38 print(user) 39 39 password=radpkt["User-Password"][0] 40 40 print(password) 41 41 # 執行sql語句 42 42 sql='select * from test where user = "%s" '% (user) 43 44 res=cursor.execute(sql) 44 46 pwd=cursor.fetchall() 45 48 cursor.close() 46 49 conn.close() 47 50 48 52 if radpkt.PwCrypt(CHALLENGE) == radpkt["User-Password"][0]: 49 53 radpkt.code = packet.AccessAccept 50 54 CHALLENGE = self.get_challenge() #挑戰碼使用過后就更換掉 51 55 print("AccessAccept") 52 56 elif radpkt.PwCrypt(pwd[0][1]) == radpkt["User-Password"][0]: 53 57 radpkt.code = packet.AccessChallenge 54 58 CHALLENGE = self.get_challenge() 55 59 radpkt.AddAttribute("Reply-Message","Enter Token Code") 56 60 radpkt.AddAttribute("State",b'0x123231')#隨機生成一個state,此處簡單寫的,可以仿照挑戰碼寫一個類似的 57 61 print("AccessChallenge, please input", CHALLENGE) 58 62 else: 59 63 radpkt.code = packet.AccessReject 60 64 print("AccessReject") 61 65 62 66 def get_pkt(self, pkt): 63 67 get_pw = None 64 68 get_name = None 65 69 radpkt = self.CreateAuthPacket(packet=pkt) #解析請求報文 66 70 #radpkt.code = packet.AccessChallenge 67 71 radpkt.secret = KEY 68 72 69 73 print("輸出相關參數") 70 74 for key in radpkt.keys(): 71 75 print(key, radpkt[key]) 72 76 if key == "User-Password": 73 77 get_pw = 1 74 78 if key == "User-Name": 75 79 get_name = 1 76 80 77 81 if 1 == get_pw and 1 == get_name: 78 82 self.check_pass(radpkt) 79 83 80 84 return radpkt.ReplyPacket() 81 85 82 87 ip_port = ('', 1812) 83 88 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp協議 84 89 server.bind(ip_port) 85 90 86 91 87 92 while True: 88 93 data,client_addr = server.recvfrom(BUFSIZE) 89 94 srv = RadiusServer() 90 95 reply = srv.get_pkt(data) 91 96 server.sendto(reply, client_addr)
驗證過程。打開ssl vpn的登錄頁面 使用數據庫表的用戶名和密碼登錄

python服務端 動態碼

輸入動態碼

登錄成功0

寫在后面:
當然這樣僅僅是雙因子認證成功而已,我們可以在結合企業微信或者釘釘機器人之類的工具,把生成的動態碼 即時的發給vpn的使用者。
