如何让Python脚本接收OAuth2.0的Code？ | Mayx的博客

1 August 2023 - 字数统计：3672 - 阅读大约需要12分钟 - Hits: Loading...

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

by mayx

这是一篇创建于 天前的文章，其中的信息可能已经有所发展或是发生改变。

AI摘要

这篇文章讨论了在Windows环境下，让Python脚本接收OAuth2.0授权码Code的几种方法。作者尝试了以下几种方式： 1. **URI Scheme**: 通过注册伪协议（如mayx://）并在Python脚本中解析回调URL来获取Code，但这种方法可能被杀毒软件阻拦，且不是所有平台都支持。 2. **HTTP服务监听**: 使用Flask创建一个简单的web服务来接收Code，虽然简单但可能占用过多资源，且已被弃用。 3. **Socket实现**: 用socket进行TCP连接来处理请求，但初次访问时有问题，且代码复杂。 4. **http.server模块**: 使用Python内置的HTTP服务器模块实现了基本功能，更小且稳定，但仍有程序结束前端口未释放的问题，后来通过AI得到了解决。 作者在寻找最合适的方案时遇到困难，感慨了解更多信息有时也带来困扰，但最终还是AI有效地解决了问题。

- 起因
- 解决方法
  - 1. 使用URI Scheme
  - 2. 使用http服务监听
    - 使用Flask实现
    - 使用socket实现
    - 使用http.server实现
- 感想

越简单，就越复杂。

#
起因

最近我在写一个在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 查看原始文件

#### 推荐文章

Loading...

上一篇：分享一种无人值守的新型诈骗手段
下一篇：关于卫星通信的一些看法
想问这篇文章
召唤伊斯特瓦尔