近日公司有个需求,需要对使用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的使用者。