驗證客戶端鏈接的合法性 如果你想在分散式系統中實現一個簡單的客戶端鏈接認證功能,又不像SSL那麼複雜, 那麼可以利用hmac+加鹽的方式來實現。 SocketServer是標準庫中的一個高級模塊(python3.x中重命名為socketserver), 它的目標是簡化很多樣板代碼,它們是創建網路客... ...
驗證客戶端鏈接的合法性
如果你想在分散式系統中實現一個簡單的客戶端鏈接認證功能,又不像SSL那麼複雜,
那麼可以利用hmac+加鹽的方式來實現。
例1:簡單的服務端如下
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import os
import socket
import hmac
secret_key = '老衲洗頭用飄柔'.encode('utf-8')
server = socket.socket()
server.bind(('127.0.0.1', 9527))
server.listen()
while True:
try:
conn, addr = server.accept()
random_bytes = os.urandom(32)
conn.send(random_bytes)
hmac_obj = hmac.new(key=secret_key, msg=random_bytes)
ret = hmac_obj.hexdigest()
print(hmac_obj.hexdigest())
msg = conn.recv(1024).decode('utf-8')
if msg == ret:
print('是合法的客戶端')
else:
print('不是合法的客戶端')
conn.close()
finally:
server.close()
break
客戶端如下:
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import socket
import hmac
secret_key = '老衲洗頭用飄柔'.encode('utf-8')
client = socket.socket()
client.connect(('127.0.0.1', 9527))
urandom = client.recv(1024)
hmac_obj = hmac.new(key=secret_key, msg=urandom)
client.send(hmac_obj.hexdigest().encode('utf-8'))
print('--------')
client.close()
效果如下:
33e40f5f66b2e9b2867a7862d02fba9d
是合法的客戶端。
例2:TCP時間戳伺服器驗證
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import os
import hmac
from socket import *
from time import ctime
'''
socket tcp時間戳服務端,利用hmac模塊加鹽驗證客戶端連接的合法性
'''
# 加鹽
secret_key = '芝麻開門'.encode('utf-8')
def conn_auth(conn):
'''
認證客戶端鏈接
:param conn: 客戶端鏈接
:return: True or False
'''
print('開始驗證新鏈接的合法性')
# os模塊生成隨機32位字元串,用於發送給客戶端驗證
ustr = os.urandom(32)
conn.sendall(ustr)
# 生成密鑰 hmac加鹽+32位隨機字元串
cipher = hmac.new(secret_key, ustr).digest()
# 接收客戶端發送過來的密鑰,長度和這邊生成的應當一致
result = conn.recv(len(cipher))
# compare 兩相比較,一致返回true,否則為false
return hmac.compare_digest(result, cipher)
def data_handler(conn,bufsize=1024):
# 如果驗證不通過
if not conn_auth(conn):
print('鏈接非法,關閉')
conn.close()
return
print('鏈接已通過驗證,開始通信')
while True:
data = conn.recv(bufsize)
if not data:break
data = '[%s] %s' % (ctime(), data.decode('utf-8'))
conn.sendall(data.encode('utf-8'))
conn.close()
def server_handler(host,port,bufsize=1024,num=5):
'''
socket tcp服務端設置
:param host: 主機名或ip地址
:param port: 埠號
:param bufsize: 緩衝區大小,預設1024
:param num: 偵聽最大客戶端,預設5位
:return:
'''
tcpss = socket(AF_INET, SOCK_STREAM)
tcpss.bind((host,port))
tcpss.listen(num)
while True:
conn, addr = tcpss.accept()
print('新連接[%s:%s]' % (addr[0], addr[1]))
data_handler(conn,bufsize)
if __name__ == '__main__':
host = 'localhost'
port = 9527
server_handler(host, port)
TCP時間戳客戶端:
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
'''
socket tcp時間戳客戶端,利用hmac模塊加鹽驗證客戶端連接的合法性
'''
import os
import hmac
from socket import *
secret_key = '芝麻開門'.encode('utf-8')
def conn_auth(conn):
'''
驗證客戶端到伺服器的鏈接
:param conn: 鏈接
:return: True or False
'''
# 客戶端接收32位隨機位元組
ustr = conn.recv(32)
# hmac加鹽加密文得出最終密鑰併發送回服務端
cipher = hmac.new(secret_key, ustr).digest()
conn.sendall(cipher)
def client_handler(host,port,bufsize=1024):
tcpsc = socket(AF_INET, SOCK_STREAM)
tcpsc.connect((host, port))
conn_auth(tcpsc)
while True:
data = input('>>>').strip()
if not data:continue
if data == 'quit':break
tcpsc.sendall(data.encode('utf-8'))
result = tcpsc.recv(bufsize)
print(result.decode('utf-8'))
tcpsc.close()
if __name__ == '__main__':
host = 'localhost'
port = 9527
bufsize = 1024
client_handler(host, port, bufsize)
效果:
服務端:
D:\PortableSoft\Python35\python.exe E:/Python/重要的代碼/socket_hmac驗證合法連接/tcpss.py
新連接[127.0.0.1:57600]
開始驗證新鏈接的合法性
鏈接已通過驗證,開始通信
客戶端:
D:\PortableSoft\Python35\python.exe E:/Python/重要的代碼/socket_hmac驗證合法連接/tcpsc.py
>>>time
[Thu May 10 22:00:19 2018] time
>>>也許豬的身體不優美,長鼻短尾,但是別人不可天空里高飛
[Thu May 10 22:01:10 2018] 也許豬的身體不優美,長鼻短尾,但是別人不可天空里高飛
>>>
socketserver
SocketServer是標準庫中的一個高級模塊(python3.x中重命名為socketserver),
它的目標是簡化很多樣板代碼,它們是創建網路客戶端和伺服器所必需的代碼。
這個模塊中有為你創建的各種各樣的類,如下表所示:
除了為你隱藏了實現細節之外,另一個不同之處是,我們現在使用類來編寫應用程式,
以面向對象的方式處理事務有助於組織數據,以及邏輯性地將功能放在正確的地方。
你還會註意到,應用程式現在是事件驅動的,這意味著只有在系統中的事件發生時,它們才會工作。
事件包括消息的發送和接收。
事實上,你會看到類定義只包括一個用來接收客戶端消息的事件處理程式。
所有其它的功能都來自使用的SocketServer類。
在原始伺服器迴圈中,我們阻塞等待請求,當接收到請求時就對其提供服務,然後繼續等待。
在此處的伺服器迴圈中,並非在伺服器中創建代碼,而是定義一個處理程式,
這樣當伺服器接收到一個傳入的請求時,伺服器就可以調用你的函數。
創建SocketServer TCP伺服器
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
'''
通過使用socketserver類、TCPServer和StreamRequesthandler,該腳本創建了一個時間戳TCP伺服器。
'''
from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime
HOST = '127.0.0.1'
PORT = 9527
ADDR = (HOST, PORT)
class MyRequestHandler(SRH):
# 重寫handle方法,該方法在基類Request中預設情況下沒有任何行為(pass)
# 但當接收到一個客戶端的消息時,它就會調用handle()方法,因此得重寫進行處理。
def handle(self):
print('...connected from:', self.client_address)
# StreamRequsetHandler將輸入和輸出套接字看作類似文件的對象
# 因此可以使用readline()獲取客戶端消息,當然此時客戶端要約定消息附帶\n換行符
data = '[%s] %s' % (ctime(), self.rfile.readline().decode('utf-8'))
# 同理,視作文件對象,使用write()將字元串發送回客戶端
self.wfile.write(data.encode('utf-8'))
tcpServ = TCP(ADDR, MyRequestHandler)
print('waiting for connection...')
# 註:是serve,而不是server;forever為無限迴圈地等待並服務於客戶端請求。
tcpServ.serve_forever()
創建SocketServer TCP客戶端
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
from socket import *
HOST = 'localhost'
PORT = 9527
BUFSIZ = 1024
ADDR = (HOST, PORT)
while True:
'''
和之前socker普通的tcp客戶端從輸入才開始迴圈不同,
sockerserver請求處理程式的預設行為是接受連接、獲取請求,然後關閉連接。
由於這個原因,我們不能在應用程式整個執行過程中都保持連接,因此每次向伺服器發送消息時,
都需要創建一個新的套接字。
'''
tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
data = input('>>>')
if not data:
break
'''
這裡使用的處理程式對待套接字通信就像是文件一樣,所以必須發送行終止符(回車和換行符)。
而伺服器只是保留並重用這裡發送的終止符。
'''
data = '%s\r\n' % data
tcpCliSock.send(data.encode('utf-8'))
resu = tcpCliSock.recv(BUFSIZ)
if not resu:
break
# 加strip()處理掉換行符
print(resu.decode('utf-8').strip())
tcpCliSock.close()
運行服務端和客戶端後效果如下:
client端:
>>>百變星君
[Wed May 9 20:44:11 2018] 百變星君
>>>大聖娶親
[Wed May 9 20:44:22 2018] 大聖娶親
>>>
server端:
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 53286)
----------------------------------------
...connected from: ('127.0.0.1', 53397)
...connected from: ('127.0.0.1', 53398)
...connected from: ('127.0.0.1', 53399)
另一種不看成文件操作的支持併發連接的TCP時間戳伺服器和客戶端如下:
socketserver TCP時間戳伺服器
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import socketserver
from time import ctime
# 併發編程
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print('...連接來自:', self.client_address)
msg = self.request.recv(1024)
msg = '[%s] %s' % (ctime(), msg.decode('utf-8'))
print(msg)
self.request.send(msg.encode('utf-8'))
if __name__ == '__main__':
# 支持重用埠和Ip
socketserver.TCPServer.allow_reuse_address = True
# ThreadingTCPServer 支持線程功能
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9527), MyServer)
print('等待連接...')
server.serve_forever()
socketserver TCP時間戳客戶端
#!/usr/bin/env python
# _*_ coding: utf-8 _*_
import socket
while True:
client = socket.socket()
client.connect(('127.0.0.1', 9527))
data = input('>>>')
if not data:
break
client.send(data.encode('utf-8'))
resu = client.recv(1024)
if not resu:
break
print(resu.decode('utf-8'))
client.close()
運行效果如下:
第一個client端:
>>>哆啦A夢
[Wed May 9 20:59:14 2018] 哆啦A夢
>>>蠟筆小新
[Wed May 9 20:59:27 2018] 蠟筆小新
>>>超人迪加
[Wed May 9 21:00:13 2018] 超人迪加
>>>
第二個client端:
>>>銀河唯一的秘密
[Wed May 9 20:59:50 2018] 銀河唯一的秘密
>>>護衛人類,輓救地球,看守這宇宙
[Wed May 9 21:00:35 2018] 護衛人類,輓救地球,看守這宇宙
>>>
socketserver服務端:
等待連接...
...連接來自: ('127.0.0.1', 53505)
[Wed May 9 20:59:14 2018] 哆啦A夢
...連接來自: ('127.0.0.1', 53506)
[Wed May 9 20:59:27 2018] 蠟筆小新
...連接來自: ('127.0.0.1', 53507)
...連接來自: ('127.0.0.1', 53508)
[Wed May 9 20:59:50 2018] 銀河唯一的秘密
...連接來自: ('127.0.0.1', 53509)
[Wed May 9 21:00:13 2018] 超人迪加
...連接來自: ('127.0.0.1', 53510)
[Wed May 9 21:00:35 2018] 護衛人類,輓救地球,看守這宇宙
...連接來自: ('127.0.0.1', 53514)
end
參考:
http://www.cnblogs.com/Eva-J/
《python核心編程第四版》