Part:2. 参数传递等其他
→2.1 查询字符串
get请求用查询字符串的方式传递参数非常简单。在前面讲的main.py文件中我们可以重新增加一段代码描述一个可以传递参数的接口。如下:
def multi(a, b):
return a * b
@app.get("/multi_by_get")
def multi_by_get(a: int, b: int):
c = multi(a, b)
return {"result": c}
从代码内容可见,定义了一个multi()方法,两个参数a/b,计算a和b的乘积并返回。multi函数为不对“外”的,并没有“装饰”成接口。对外的接口我们使用multi_by_get() 实现。接口需要输入两个参数,并调用multi()方法,显示最终结果a和b的乘积给get请求。

→2.2 GET和POST区别
从字面上来讲,Get应该是用来从服务器上获得数据,而Post是用来向服务器上传递数据。
通常,GET用于获取信息,是无副作用的,是幂等的,且可缓存,POST用于修改服务器上的数据,有副作用,非幂等,不可缓存。幂等的意思可以简单地理解为多次操作后的结果是同样的。
GET和POST只是HTTP协议中两种请求方式,而HTTP协议是基于TCPIP的应用层协议,无论GET还是POST,用的都是同一个传输层协议,所以在传输上,没有区别。
这里要说的区别是在接口开发过程中的一些细节上的差别。
-
请求方法不一样 (HTTP请求头) -
参数: -
GET 参数通过查询字符串传递,字符串在HTTP的请求头中 -
POST 参数通过json(HTTP 正文)
下面来看看post通过json传参的例子。在修改main.py文件中post传参接口前,我们先另外创建一个model.py文件用来定义需要传递的参数,model.py如下:
from pydantic import BaseModel
class Item(BaseModel):
a: int
b: int
Item类继承自BaseModel,类变量a和b定义了post接口需要接受的参数及类型。那么回到main.py文件,我们添加如下代码段,定义post请求的接口,如下:
from model import Item
@app.post("/multi_by_post")
def multi_by_post(item: Item):
c = multi(item.a, item.b)
return {"result": c}
可以看到,这里multi_by_post接收的参数为一个Item对象。参数由item.a和item.b传入。我们在Apipost客户端中进行测试,注意不能在浏览器中测试,前面已经讲过了。浏览器默认是get请求。

question?post可否修改URL实现,查询字符串传参。
ans:可以
为什么可以呢,我们可以把前面的multi_by_post()接口稍微做个修改进行测试。修改如下:
@app.post("/multi_by_post")
def multi_by_post(a: int, b: int):
c = multi(a, b)
return {"result": c}
注意了,这里修改后的代码跟之前的multi_by_get()唯一的区别就是@app.post接口对接的请求方式不同。由于还是post请求,我们还是不能在浏览器中测试,如果在浏览器中测试,依然会出现下图中的情况:

我们还是在Apipost客户端中进行测试发现测试成功,我们同样采用类似于get请求的查询字符串方法,在URL中给出a=100和b=300,结果返回result:30000。

Part:3. 接口安全
接口的安全一定是基于密码学的,而不是HTTP请求方法的
-
HTTPS -
接口安全和加密
鉴权方式:
-
token -
api-key -
Json Web Token(JWT) -
cookie: 保存在服务器中的一个数据 -
session_id 根据id把不同的请求关联到session
状态码:4XX 请求端错误;404 表示不存在, 401 表示没提供身份信息, 403 提供了身份信息,权限不足。
→3.1 第一个login例子
我们创建的接口只想开放给有权限的用户使用,这是一个非常常见的场景。那么怎么去实现。先来看一个简单例子。
首先接口的入口肯定要将用户名和密码之类的作为参数传递,进行鉴权。于是我们在model.py中加入几行代码如下:
class LoginInfo(BaseModel):
username: str
password: str
这里就是给定一个参数传递用户信息的数据类型,用作post请求的时候传递json格式的参数。回到main.py文件,我们加入代码给出接口登录验证代码如下:
from fastapi import FastAPI, HTTPException
_data = {
'user': {
"username": 'chenji',
"password": '123'
}
}
@app.post("/login")
def login(login_info: LoginInfo):
if login_info.username != _data['user']['username']:
raise HTTPException(status_code=400, detail="username is wrong")
if login_info.password != _data['user']['password']:
raise HTTPException(status_code=400, detail="password is wrong")
return {"token": " "}
这里可以看见’/login’作为登录接口地址接收post请求。
_data 为我们预设的一个用户数据,用户名为”chenji”,密码为”123″。用作验证用户请求信息是否正确用的。实际场景中,这里的信息应该是事先安排好的用户数据库中获取的,这里作为例子我简化了。
login()方法内部就是两个逻辑验证,一个是用户名是否正确,如果错误,raise一个HTTP中断。如果密码错误,同样raise一个HTTP中断。
如果用户名和密码都验证成功,则返回一个token。
我们同样可以在Apipost中做个测试。用户名错误、密码错误、用户名、密码均正确分别如下图所示。



→3.2 token如何变得安全
token(也称令牌)就是一串字符,其作用是为了减轻频繁进行用户名和密码的验证而对服务器产生的压力。客户端首次访问通过正确的用户名和密码从服务端拿到 token,之后客户端携带 token 即可完成验证,无需重复携带用户名和密码。
所以前面login的例子,我们就是要拿到用户名和密码进行鉴权,然后分配token。前面例子将返回的token设置为” “(空格字符)。现在我们来实现token生成这步。
常用的生成token的库
-
python-jose -
pyjwt -
jwcrypto -
authlib
我们例子中用python-jose,可以自行在python环境中安装一下。
先在main.py和model.py文件夹中另外再创建一个文件libs.py,写入下列代码:
import datetime
from jose import jwt
KEY = "dkfkjdkgkgn083497957kdjf573434"
TOKEN_EXPIRE = 60 * 24 * 7 # token 有效期 1 week
def create_token(username: str):
d = {
"username": username,
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=TOKEN_EXPIRE)
}
encoded = jwt.encode(d, KEY, algorithm='HS256')
return encoded
里面的KEY(密钥)是我随意写的一串字符串,我们用来生成token时需要用到。TOKEN_EXPIRE是设定token的有效期,我们在create_token中会用到。encoded就是我们用HS256算法加密后得到的token并返回。
JWT签名算法中,一般有两个选择,一个采用HS256,另外一个就是采用RS256。签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接收方验证信息是否被篡改的依据。RS256 (采用SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据URL)。另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。另外,如果你无法控制客户端,无法做到密钥的完全保密,RS256会是个更佳的选择,JWT的使用方只需要知道公钥。由于公钥通常可以从元数据URL节点获得,因此可以对客户端进行进行编程以自动检索公钥。如果采用这种方式,从服务器上直接下载公钥信息,可以有效的减少配置信息。
然后回到main.py文件,我们仅仅需要导入create_token()并修改login接口的返回代码如下即可进行“用户+验证+加密token”返回的过程。
from libs import create_token
@app.post("/login")
def login(login_info: LoginInfo):
if login_info.username != _data['user']['username']:
raise HTTPException(status_code=400, detail="username is wrong")
if login_info.password != _data['user']['password']:
raise HTTPException(status_code=400, detail="password is wrong")
return {"token": create_token(login_info.username)}
重新到Apipost中进行测试。我们就可以看到下图中成功生成了有效期为一周的token了。

→3.3 如何利用token把接口保护起来
前面虽然实现了login接口返回一个根据用户信息和KEY返回的加密token,但是我们之前写的/multi_by_post等接口并没有被token所保护起来,用户依然可以直接进行请求。
这里我们要实现一下,不提供授权用户信息,无法访问接口的功能。这个很重要。
其实利用token保护其他接口的逻辑非常简单。就是让请求者在访问功能性接口的时候提供代表有权用户信息的token。当然实际操作中我们是将用户提供的token解码成用户名,并判断用户名是否合法。
比如,我们可以针对原来的/multi_by_post接口做如下的修改。
from fastapi import FastAPI, HTTPException, Depends
@app.post("/multi_by_post")
def multi_by_post(a: int, b: int, user: str = Depends(get_user)):
c = multi(a, b)
return {"result": c}
这里的修改就是给multi_by_post增加了一个参数,并通过Depends来在传参前提供依赖。修改后,这时候我们再使用查询字符串的方法对/multi_by_post发送请求,就会提示未通过验证了。如下图所示:

当然这里的get_user方法需要在libs.py中实现一下。其实质就是将login中验证过生成的token还原成用户信息。代码如下:
from fastapi.security import OAuth2PasswordBearer
def get_user(token: str = Depends(OAuth2PasswordBearer(tokenUrl='/login'))):
# 解析token得到用户信息
try:
d = jwt.decode(token, KEY, algorithms='HS256')
username = d.get("username", "-1")
except(jwt.JWTError,):
raise HTTPException(status_code=403, detail="token is wrong")
if username == '-1':
raise HTTPException(status_code=400, detail="username is wrong")
return username
验证方式选择OAuth2PasswordBearer。这时候我们重新在Apipost中进行保护起来的/multi_by_post接口测试。在认证中选择bear,填入之前我们生成的token就可以得到正确的返回了。如下图所示:

当然我们也可以在请求头header中传入token认证信息,如下:

原文始发于微信公众号(拒绝拍脑袋):接口API开发系列(2)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/54800.html