从零开始写博客系统——权限校验
背景
如果读者按照前面的文章认真的从头到尾码了代码并且能正常运行,那么其实已经是一个简易的博客系统了,但是这个简易的博客系统还有最后一个问题,那就是我们的新增,修改接口是不能被人随便调用的,否则别人任意的写数据,修改你的文章,那么这个系统也就失去了价值,因此本文作为博客系统后台的一个收尾,要把这些基本的鉴权加上。
设计思路
鉴权的思路其实很简单,说到底就是这个请求带有一个别人不知道,只有你知道的东西到了服务器,那么服务器就允许这笔请求运行下去,否则就不允许运行。所有的鉴权,都是这个原理。
那么基于上面的原理,其实只要在接口字段新增一个参数,这个参数可以任意定义,比如除了常规参数之外,额外增加一个参数叫abcdefg
,abcdefg
这个参数的值必须是uhgiuhgwohgo
,否则就不是本人请求的,服务器拒绝。这么做最简单粗暴,但是可以看到,市面上几乎没有任何系统采用这种方式鉴权,因为这么做请求参数就会有一个跟业务毫无关系的字段,而这样一个字段需要在所有鉴权的接口都写一遍。
在HTTP
协议上,比较通用的做法,就是在Cookie
中做这个校验。前端就需要在请求的时候带上这个Cookie
,那么前端就需要拿到这个Cookie
,因此就需要有一个登陆接口来获取Cookie
上代码
有一个登陆接口,那么就会有用户校验。因此我们需要新增一个文件,用这个文件来做用户的校验。
# user.py
user_list = [
{
"user": "admin",
"pass": "admin"
},
{
"user": "viewer",
"pass": "guest"
}
]
class User(object):
def __init__(self, user, passwd) -> None:
self._user = user
self._pass = passwd
def is_admin(self):
if self._user != "admin":
return False
for u in user_list:
if u["user"] == self._user and u["pass"] == self._pass:
return True
return False
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这个文件就是用来校验用户是否是管理员,只有管理员我们才允许新增和修改文章。
接下来增加登陆接口。
#app.py
@app.route("/login", methods=["POST"])
def login():
username = request.get_json().get("username")
password = request.get_json().get("password")
u = user.User(username, password)
if u.is_admin():
resp = make_response({"code": 0, "msg": "success"})
resp.set_cookie("user", "admin", max_age=3600)
return resp
else:
return jsonify({"code": -1, "msg": "fail"})
2
3
4
5
6
7
8
9
10
11
12
13
当然,新增接口和修改接口,我们都需要增加额外的校验。
#app.py
@app.route("/add_article", methods=["POST"])
def add_article():
ck = request.cookies.get("user")
if ck != "admin":
return jsonify({"code": 1, "msg": "fail", "data": {}})
。。。。。。
@app.route("/update_article", methods=["POST"])
def update_article():
ck = request.cookies.get("user")
if ck != "admin":
return jsonify({"code": 1, "msg": "fail", "data": {}})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
验证接口
我们运行服务之后,我们调用登陆接口。
curl -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "admin"}' http://127.0.0.1:5000/login -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> POST /login HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 42
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Werkzeug/2.1.1 Python/3.9.10
< Date: Tue, 10 May 2022 14:56:58 GMT
< Content-Type: application/json
< Content-Length: 27
< Set-Cookie: user=admin; Expires=Tue, 10 May 2022 15:56:58 GMT; Max-Age=3600; Path=/
<
{"code":0,"msg":"success"}
* Connection #0 to host 127.0.0.1 left intact
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到,返回的数据里面,Set-Cookie
字段已经有了我们设置的信息。
当然,如果是用前端请求,这个Cookie就会在浏览器中保存。前端页面我们在下一章进行说明。
补充
实际上,上面这种方式是一种实现,前端那道这个信息之后,自己去跳转地址。还有一种实现方式就是在后端来控制跳转的地址。
@app.route("/login", methods=["POST"])
def login():
username = request.get_json().get("username")
password = request.get_json().get("password")
u = user.User(username, password)
if u.is_admin():
resp = make_response(redirect("/dashboard", 302))
resp.set_cookie("user", "admin", max_age=3600)
return resp
else:
return jsonify({"code": -1, "msg": "fail"})
2
3
4
5
6
7
8
9
10
11
实际的差异就是这个redirect
的代码。
curl -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "admin"}' http://127.0.0.1:5000/login -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> POST /login HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 42
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 FOUND
< Server: Werkzeug/2.1.1 Python/3.9.10
< Date: Mon, 16 May 2022 13:48:22 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 226
< Location: /dashboard
< Set-Cookie: user=admin; Expires=Mon, 16 May 2022 14:48:22 GMT; Max-Age=3600; Path=/
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Connection #0 to host 127.0.0.1 left intact
<p>You should be redirected automatically to target URL: <a href="/dashboard">/dashboard</a>. If not click the link.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
运行结果就会有这个重定向的信息,其中返回码302
就会告诉浏览器,这个请求是一个重定向的返回,返回头里面的Location
就表示要重定向的地址,同时设置了Cookie
的值。
浏览器拿到这个信息之后就会自动去跳转。这些实现方式都是可以的,因此读者可以按需来设计。
总结
本文我们描述了我们的登陆接口,了解了常规的权限校验方案。
同样,我们的代码放在:代码浏览 - blog - 从零开始写一个博客系统 - svenweng (coding.net) chapter-9
重定向的版本代码放在:代码浏览 - blog - 从零开始写一个博客系统 - svenweng (coding.net) chapter-9-1
思考
我们把新增接口和修改接口增加了权限校验,希望读者能够自己测一下,同时我们的测试用例也需要进行修改,这部分内容也由读者自己修改。