FastAPI用户安全性解决方案

1.需求分析

  1. 用户登录验证
  2. 用户登录保持

2.实现思路

用户登录和令牌发放

  1. 用户发送用户名和密码到服务器。
  2. 服务器检查从数据库中查找用户名和密码哈希值(服务器不保存密码,只保存密码的哈希值)。
  3. 计算密码哈希值,与服务器中数据一致则进入下一步。
  4. 生成令牌,令牌内容包括用户名和失效日期,还可以包含其他信息,加密成一段字符串,即Token。
  5. 服务器将加密后的令牌(Token)返回到前端。
  6. 前端脚本将令牌保存到LocalStorage。

用户登录状态保持

  1. 前端每次向服务器发送请求,都把令牌放在请求头中。
  2. 服务器从请求中读取令牌。
  3. 对令牌内容进行解译,解译成功则进行下一步。
  4. 根据令牌内容和数据库中的信息,判断用户权限。
  5. 根据权限返回用户私有内容。

3.支持模块

后端支持:
pip install fastapi[all]

pip install passlib //密码算法库

pip install python-jose[cryptography]  //token创建和解码
  1. 用于校验密码的哈希算法。
  2. 令牌计算和解译工具。
  3. 数据库和数据库连接工具,为简化操作,这里使用文件存储代替。
  4. 路由处理。
  5. 跨越请求。
前端支持:
  1. ajax请求模块。
  2. 本地端口服务。

4.文件组织

采用前后端分离方法。前端和后端分别放在不同的文件夹。
后端文件如下:

main.py //主文件
my_tools.py//密码校验和令牌创建、解译工具,包含类PasswordCheck,TokenCreator
my_fake_db_connect.py//模拟数据库连接工具
fake_data.json//用json文件模拟数据库

5.后端实现代码

main.py

main.py文件主要作用是处理路由请求,配置跨域许可,调用各类对象和方法,其中关键代码在路由方法中。

#文件名:main.py
from fastapi import FastAPI,Depends,HTTPException, status
from fastapi.middleware.cors import CORSMiddleware#跨域请求
from my_fake_db_connect import fake_db_connect#虚拟数据库连接
from my_tools import PasswordCheck,TokenCreator
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")#初始化安全性方案对象

#初始化工具类
tokencreator=TokenCreator()
passwordcheck=PasswordCheck('fake_data.json')
db=fake_db_connect('fake_data.json')

# 放行跨域请求的域名

origins = [
    "http://localhost",
    "http://localhost:8080",
    "http://localhost:8848",
    "http://127.0.0.1:8848",
    "http://127.0.0.1:5500"
]

app=FastAPI()

# 添加跨域方案
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)



#游客的访问
@app.get("/hello")
async def hello(name:str):
    return "hello,"+name+"!"

#登录,返回令牌
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    '''
    处理登录请求
    '''
    #user = authenticate_user(
    #    fake_users_db, form_data.username, form_data.password)
    username=form_data.username
    password=form_data.password

    if not passwordcheck.check(username, password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )

    access_token=tokencreator.create(username)
    print('返回用户令牌',access_token)
    # 返回用户令牌
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/user/me")
async def get_current_user(token: str = Depends(oauth2_scheme)):
    '''
    获取当前用户信息。
    '''
    # 默认权限错误信息
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    # 尝试解码token获取username
    username=tokencreator.decode(token)
    
    if username is None: raise credentials_exception

	# 尝试从数据库取用户信息
    try: userinfo=db.get(username)
    except: raise credentials_exception
       
    if userinfo is None: raise credentials_exception
    
    return userinfo
    

my_tools.py

这里封装了本案例中最核心的方法。即密码的校验、令牌的生成和解译。

class PasswordCheck():
    '''
    密码校验工具。
    初始化方法:password_check=PasswordCheck("数据库名")。
    密码检查方法:.check("用户名","密码"),返回布尔类型。
    '''
    from my_fake_db_connect import fake_db_connect#数据库连接模块
    from passlib.context import CryptContext#哈希算法模块
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")#定义一个哈希算法

    def __init__(self,db:str):#
        self.db=self.fake_db_connect(db)
        pass


    def check(self,username:str,password:str)->bool:
        try:
            userinfo=self.db.get(username)#尝试取数据
            hashed_password=userinfo.get('hashed_password')
            if self.pwd_context.verify(password, hashed_password):
                return True
            else: return False
        except:
            return False
      

    def __call__(self,username:str,password:str)->bool:
        self.check(username, password)


class TokenCreator():
    '''
    令牌生成器。
    '''
    #算法库
    from jose import JWTError, jwt

    from datetime import datetime, timedelta
    from typing import Optional

    #令牌加密方法和有效期
    SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
    ALGORITHM = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES = 30


    def __init__(self):
        pass
    
    def __call__(self,username:str):
        self.create(username)

    def create(self,username:str,expires_delta:Optional[timedelta] = None)->str:
        if expires_delta:
            # 失效时间等于当前时间加有效期
            expire = self.datetime.utcnow()+expires_delta
        else:
            expire = self.datetime.utcnow()+self.timedelta(minutes=15)

        data={"sub":username,'exp':expire}#写入令牌的数据
        encode_jwt = self.jwt.encode(data, self.SECRET_KEY, algorithm=self.ALGORITHM)#生成令牌
        return encode_jwt

    def decode(self,token:str)->str:
        '''
        解码Token,返回用户名
        '''
        try:
            payload = self.jwt.decode(token, self.SECRET_KEY, algorithms=[self.ALGORITHM])
            username: str = payload.get("sub")
            if username is None:
                return None

        except self.JWTError:
            return None
            print('解码令牌出错')
        return username

my_fake_db_connect.py

这里使用JSON文件模拟数据库存储,使用python自带功能模拟数据库的增删改查,这样做数据丢失风险极大,这里仅作为测试时的替代方法,实际工作中务必使用真正的数据库。

class fake_db_connect():
    '''
    模拟数据库连接工具
    '''
    import json
    def __init__(self,filename:str):
        self.filename=filename
        with open(filename, 'r',encoding='utf-8') as f:
            self.fake_user_table = self.json.load(f)
    
    def get(self,primaryKey:str)->dict:
        return self.fake_user_table.get(primaryKey)

    def insert(self,info:dict):
        primaryKey=info['username']
        self.fake_user_table[primaryKey]=info

    def commit(self):
        with open(self.filename, 'w',encoding='utf-8') as f:
            self.json.dump(self.fake_user_table, f, ensure_ascii=False,indent=2)
    
    def delete(self,primaryKey:str):
        del self.fake_user_table[primaryKey]

fake_data.json

{
  "johndoe": {
    "username": "johndoe",
    "full_name": "John Doe",
    "email": "johndoe@example.com",
    "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
    "disabled": false
  },
  "alice": {
    "username": "alice",
    "full_name": "Alice Wonderson",
    "email": "alice@example.com",
    "hashed_password": "fakehashedsecret2",
    "disabled": true
  },
  "lihua": {
    "username": "lihua",
    "full_name": "Li Hua",
    "email": "lihua@example.com",
    "hashed_password": "fakehashedmima",
    "disabled": true
  }
}

6.前端测试代码

这里采用axios进行测试,需要先把vue.global.jsaxios.min.js 文件下载到本地,使用CDN引用方式会引起跨域问题。
测试时不能直接用浏览器打开文件进行测试,需要启动本地服务,可以使用HBuilder X,如果用VS Code,可以按照Live Server 插件,快速启动服务。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
		<script src="./js/vue.global.js"></script>
		<script src="./js/axios.min.js"></script>


		</script>
	</head>
	<body>

		<div id="app">
			<h2>表单测试</h2>

			<form action="http://127.0.0.1:8000/token" method="post">
				<label>用户名:</label><input type="text" name="username" id="username" value="johndoe" /><br>
				<label>密码:</label><input type="text" name="password" id="password" value="secret" /><br>
				<input type="submit" id="" name="" />
			</form>

			<div>

				<h2>GET测试</h2>
				<button @click="test()">测试</button>
				{{info}}

				<h2>ajax表单登录测试</h2>

				<button type="button" @click="userlogin()">登陆测试</button>

			

				<h2>登录后GET测试</h2>

				<button type="button" @click="get_my_info">当前用户</button>
				<br>
				返回值{{my_info}}
				<br>
				返回信息:
				<li v-for="(value,key) in my_info">
					{{key}} &nbsp; : &nbsp; {{value}}
				</li>
			</div>



		</div>
		<script>
			

			const app = {
				data() {
					return {
						info: 'Ajax 测试!!',

						
						username: "johndoe",
						password: "secret",
						
						my_info: ''
					}
				},
				mounted() {

				},
				methods: {
					test() {
						axios({
								method: 'get',
								url: 'http://127.0.0.1:8000/hello',
								params: {
									name: 'lihua',
									age: '22'
								}
							})
							.then(response => (this.info = response.data))
							.catch(function(error) { // 请求失败处理
								console.log(error);
							});
					},
					userlogin() {
						axios
							({
								method: "post",
								url: "http://127.0.0.1:8000/token",
								headers: {
									'Content-type': 'application/x-www-form-urlencoded'
								},
								data: "username=johndoe&password=secret",

							})
							.then(function(response) {
								this.tokeninfo = response.data;
								localStorage.Token = this.tokeninfo.access_token;
								console.log(localStorage.Token);
								console.log('登录成功!')
							})
							.catch(function(error) { // 请求失败处理
								console.log(error);
								this.tokeninfo = "登录失败";
							});
							
					},
					get_my_info() {
						axios({
								method: "get",
								url: "http://127.0.0.1:8000/user/me",
								headers: {
									'Authorization': 'Bearer ' + localStorage.Token
								},
							})
							.then(response => (this.my_info = response.data))
							.catch()
					}

				}

			}

			Vue.createApp(app).mount('#app')
		</script>

	</body>
</html>

测试结果:
在这里插入图片描述

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
FastAPI 是一个基于 Python 的现代、快速(高性能)的 Web 框架,它提供了一些内置的安全性功能来保护应用程序免受常见的 Web 攻击。下面是一些 FastAPI安全性问题和相应的解决方案: 1. 跨站脚本攻击(XSS):XSS 攻击是指攻击者通过在网页中注入恶意脚本来获取用户敏感信息或执行恶意操作。FastAPI 使用 Jinja2 模板引擎来自动转义用户输入,以防止 XSS 攻击。此外,使用 FastAPI 的模型验证功能可以过滤和验证用户输入,从而进一步减少 XSS 攻击的风险。 2. 跨站请求伪造(CSRF):CSRF 攻击是指攻击者通过伪造用户的身份执行未经授权的操作。FastAPI 提供了 CSRF 保护中间件,可以生成和验证 CSRF 令牌,以确保请求来自合法的来源。 3. 认证和授权:FastAPI 支持多种认证和授权方式,包括基于令牌的身份验证(如 JWT)、OAuth2 和 OpenID Connect。你可以使用 FastAPI 的内置认证和授权功能,或者集成其他第三方库来实现更复杂的认证和授权需求。 4. 敏感数据泄露:FastAPI 提供了一些内置的安全性功能来保护敏感数据的泄露,如请求体和响应体的自动验证和转换、密码哈希和加密等。你可以使用这些功能来确保敏感数据在传输和存储过程中的安全性。 5. 日志和监控:FastAPI 提供了强大的日志和监控功能,可以记录和监控应用程序的运行状态,包括请求和响应的详细信息。这些功能可以帮助你及时发现和应对潜在的安全问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高堂明镜悲白发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值