6

如何让Python脚本接收OAuth2.0的Code?

 1 year ago
source link: https://mabbs.github.io/2023/08/01/auth.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1 August 2023 - 字数统计:3672 - 阅读大约需要11分钟 - Hits: 32

如何让Python脚本接收OAuth2.0的Code?

by mayx


越简单,就越复杂。

最近我在写一个在Windows上运行的Python脚本,需要做一个类似于第三方登录的功能。一般来说像这种东西都是使用的OAuth2.0来做认证,我接入的平台没有悬念的也是使用的这个东西,其中的重点就是获取到第三方网站的授权码Code,然后用它换取用户信息。于是我就在想怎么做比较好呢?

1. 使用URI Scheme

我看VSCode在实现类似功能的时候似乎用到了URI Scheme,就是在系统中注册一个伪协议,然后通过这个伪协议来调用程序实现获取Code,方法很简单,首先先在注册表里导入:

Windows Registry Editor Version 5.00  
  
[HKEY_CLASSES_ROOT\mayx]  
"URL Protocol"="D:\\mayx.exe"  
@="MayxProtocol"  
  
[HKEY_CLASSES_ROOT\mayx\DefaultIcon]  
@="D:\\mayx.exe,1"  
  
[HKEY_CLASSES_ROOT\mayx\shell]  
  
[HKEY_CLASSES_ROOT\mayx\shell\open]  
  
[HKEY_CLASSES_ROOT\mayx\shell\open\command]  
@="\"D:\\mayx.exe\" \"%1\""  

然后编写一个Python脚本并用Pyinstaller打包:

import sys
print(sys.argv[1])

最后将回调地址填为“mayx://get”,这样认证完成之后就会像这样调用程序:“D:\mayx.exe mayx://get?code=something”,再用urllib简单做个解析就完成了。
这样看起来是不是非常的简单?看起来确实是挺简单的,可惜坑比较大,第一个是像上述这样的注册表有些杀毒软件会拒绝导入,因为像“shell\open\command”这种东西被病毒啥的滥用的地方太多了,除非软件通过了数字签名或者某些认证,否则正常情况下根本没法导入。二是不是所有的第三方平台都支持伪协议的方式调用,不过我试了一下我用的那个平台倒是支持😂,除了杀毒软件比较讨厌之外其他的倒还好。

2. 使用http服务监听

很多跨平台的程序,像有些主要在Linux上运行的某些程序就喜欢在获取Code的时候启动一个简单的web服务。其实用这个方法的话可能还更方便一点,不容易出问题。

使用Flask实现

最开始,我想着要不然就用Flask来实现这个功能吧,实现起来也简单,写起来就几行:

from flask import Flask, request
code = ""
app = Flask(__name__)
@app.route('/getcode')
def get():
    global code
    code = request.args.get("code")
    shutdown = request.environ["werkzeug.server.shutdown"]
    shutdown()
    return "OK"

app.run(host="127.0.0.1",port=8000)
print(code)

看起来也确实很简单,功能实现的也很完美,但是有个问题是总感觉用Flask做这么简单的事情实在是大材小用,打包成程序也要占不少空间,另外就是这个方法已经被弃用了,感觉就是很不爽。那要说不爽的话怎么才爽呢?

使用socket实现

我想起来之前期末时写的期末作业socket-bbs,这就是用socket实现的一个简单的论坛,那我这么简单的功能想来用socket实现不是很好吗?于是就写了一个:(这段代码没有测试,因为这个方法不能用所以把代码删了,这是重新做的)

import socket
import urllib.parse
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #打开一个网络连接
server.bind(('127.0.0.1',8000)) #绑定要监听的端口
server.listen(5)  # 设置最大的连接数量为5
code = ""
while True:
    sock, addr = server.accept()  # 建立客户端连接
    data = sock.recv(8192).decode('utf-8').split('\r\n')#接收TCP数据,数据以字符串的形式返还
    if not data[0]:
        sock.close()  # 关闭连接
        continue
    url = urllib.parse.urlparse(data[0].split()[1])
    if url.path == '/getcode':
        query = urllib.parse.parse_qs(self.data)
        code = query["code"][0]
        sock.send(("HTTP/1.0 200 OK" + '\r\n').encode('utf-8'))
        sock.send(("Content-Type: text/html; charset=utf-8" + '\r\n').encode('utf-8'))
        sock.send('\r\n'.encode('utf-8'))
        sock.send("OK".encode('utf-8')) #发送TCP数据
        sock.close()  # 关闭连接
        break
    else:
        sock.send(("HTTP/1.0 404 Not Found" + '\r\n').encode('utf-8'))
        sock.send(("Content-Type: text/html; charset=utf-8" + '\r\n').encode('utf-8'))
        sock.send('\r\n'.encode('utf-8'))
        sock.send("Not Found".encode('utf-8')) #发送TCP数据
        sock.close()  # 关闭连接
print(code)

看起来是不是复杂多了?其实大多数时候工作的好像挺正常的,但是不知道为什么莫名其妙第一次访问的时候会卡住,改了半天头都大了,于是就只好放弃这种方法了……

使用http.server实现

这时候我突然想到我平时用来传文件用的python自带的模块http.server,既然这个模块是自带的,大小应该大不到哪去吧?随后我就写了一个:

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import urllib.parse
code = ""
class Resquest(BaseHTTPRequestHandler):
    timeout = 5
    def do_GET(self):
        url = urllib.parse.urlparse(self.path)
        if url.path == "/getuser":
            self.send_response(200)
            self.send_header("Content-type", "text/html")  # 设置服务器响应头
            code = urllib.parse.parse_qs(url.query)["code"][0]
            buf = '''OK'''
            self.wfile.write(buf.encode())
            self.server.shutdown()
        else:
            self.send_response(404)
            self.send_header("Content-type", "text/html")  # 设置服务器响应头
            self.end_headers()
            buf = '''Not Found'''
            self.wfile.write(buf.encode())
host = ("127.0.0.1", 8000)
server = ThreadingHTTPServer(host, Resquest)
print("Starting server, listen at: %s:%s" % host)
server.serve_forever()
server.socket.close()
print(code)

最后打包试了一下,功能正常,虽然还是大了点,不过比Flask小😁。其实我在写这个代码的时候,最开始不知道要在最后加上server.socket.close(),一开始写的时候总是遇到程序执行完之前端口必定不会释放,令人很烦,直接搜也没什么好的结果,不过问了下ChatGPT立马就解决了🤣,它说:

当你调用server.shutdown()来停止服务器时,它会停止接收新的连接,并关闭已有的连接。但是,由于Python的socket库的设计,socket对象并不会立即释放端口。相反,它会在一段时间内处于TIME_WAIT状态,以确保在网络中所有挂起的数据都被正确传递或丢弃。这是一种网络协议的要求,称为”TCP TIME_WAIT”状态。

AI还真是方便啊……

解决这么一个小问题却一时半会拿不下最合适的方案,这难道就是了解太多的副作用吗😝,不过最终来看还是AI厉害,一下子就解决了我的问题。

tags: Python - 服务器 - OAuth

0 comments

Be the first person to leave a comment!


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK