OAuthLib
OAuth:一个用于授权的API标准。可以实现常见的第三方登录。
- 常用的版本是OAuth2,它不兼容OAuth1。
- OAuth的目的是:用户想通过平台A的账号直接登录第三方应用,第三方应用想得到用户的token,从而有权限访问用户在平台A上的某些资源(比如用户名、邮箱)。
笔者认为,OAuth的主要难点是怎么构造请求参数、哪些参数是必要的。就算勉强实现OAuth了,也不怎么规范,也不知道有什么漏洞。
看了OAuth标准的文档一段时间之后,笔者发现了一个Python的OAuth框架OAuthLib,它提供了关于OAuth1.0、OAuth2.0的通用函数。
- 使用
pip install oauthlib
即可安装它。 - 它并不能把OAuth流程的代码简化多少,但是可以使OAuth流程更规范、安全。
- 它的函数是通用的,在Flask、Django中同样可以使用它。
笔者主要用到两个函数:
- prepare_request_uri(…)
OAuth流程的一开始,第三方应用要把用户重定向到平台A的登录URL。
用这个函数可以生成登录URL,优点是能检查输入的参数是否规范。 - prepare_request_body(…)
OAuth流程的最后,第三方应用要拿着code发出POST报文到平台A,请求获取token。
用这个函数可以生成POST报文的body。
通过Gitlab认证的例子
下面给出一个例子:基于Flask编写一个app,在Gitlab上注册,让用户可以通过Gitlab的账号登录app。
-
首先,在Gitlab上注册app,需要输入app的名字、回调URL。注册成功会被分配一个client_id、client_secret。
-
安装依赖库:
pip install requests flask oauthlib
-
运行app的服务器:
import os from urllib.parse import urlencode import requests from flask import Flask, jsonify, redirect, render_template, request, session from oauthlib import oauth2 app = Flask(__name__) app.secret_key = os.urandom(24) OAUTH = { # 配置参数,如果没有设置正确,OAuth流程就会失败 "client_id": "6c6d1d870a45ec568f4eesaba548862dd907045a00471001b88201xdf6fc9364", "client_secret": "ab94b30cb1sd65885c9f05evf8b1a47c8de0ef96b2e1b2b5028a192e05g65e38", "redirect_uri": "http://192.168.0.1/login/oauth/callback/", "scope": "api", # 表示OAuth请求授权的范围,Gitlab上选择了"api" "auth_url": "http://gitlab.test/oauth/authorize", "token_url": "http://gitlab.test/oauth/token", "api_url": "http://gitlab.test/api/v4", } os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true" # 允许使用HTTP进行OAuth @app.route("/login/oauth/", methods=["GET"]) def oauth(): """ 当用户点击该链接时,把用户重定向到Gitlab的OAuth2登录页面。 """ client = oauth2.WebApplicationClient(OAUTH["client_id"]) state = client.state_generator() # 生成随机的state参数,用于防止CSRF攻击 auth_url = client.prepare_request_uri(OAUTH["auth_url"], OAUTH["redirect_uri"], OAUTH["scope"], state) # 构造完整的auth_url,接下来要让用户重定向到它 session["oauth_state"] = state return redirect(auth_url) @app.route("/login/oauth/callback/", methods=["GET"]) def oauth_callback(): """ 用户在同意授权之后,会被Gitlab重定向回到这个URL。 """ # 解析得到code client = oauth2.WebApplicationClient(OAUTH["client_id"]) code = client.parse_request_uri_response(request.url, session["oauth_state"]).get("code") # 向Gitlab请求获取token body = client.prepare_request_body(code, redirect_uri=OAUTH["redirect_uri"], client_secret=OAUTH["client_secret"]) r = requests.post(OAUTH["token_url"], body) access_token = r.json().get("access_token") # 查询用户名并储存 api_path = "/user" url = OAUTH["api_url"] + "/" + api_path + "?" + \ urlencode({"access_token": access_token}) r = requests.get(url) data = r.json() session["username"] = data.get("username") session["access_token"] = access_token # 以后存到用户表中 return redirect("/") @app.route("/logout/", methods=["GET"]) def logout(): session.pop("username", None) return redirect("/") @app.route("/", methods=["GET"]) def home(): username = session.get("username") if username: context = {"title": "主页", "msg": "你已登录:{}".format(username), "url": "/logout/", "url_name": "登出"} return render_template("common.html", **context) else: context = {"title": "主页", "msg": "你还没有登录。", "url": "/login/oauth/", "url_name": "通过Gitlab登录"} return render_template("common.html", **context) if __name__ == "__main__": app.run(host="0.0.0.0", port=80, debug=True)
-
用户访问app的网址,会看到如下页面:
-
当用户点击“通过Gitlab登录”之后,就会被app重定向到Gitlab的登录URL。Gitlab会询问用户是否同意授权给app,如下:
-
当用户同意授权之后,就会被重定向回到app。app会解析出授权码code,然后在后台向Gitlab请求获取token,OAuth完成。