这里通过代码讲述了使用python
生成以及校验图片验证码,增强用户认证安全性的过程。
客户端则使用 vue3
和 vuetify3
框架使用后台API生成的图片验证码。
生成验证码
主要思路是:随机生成字母和数字,使用随机的颜色创建白色背景上的验证码图片,再增加随机颜色的干扰线、干扰点以及干扰圆圈。主要逻辑代码如下:
from PIL import Image, ImageDraw, ImageFont
from random import randint, choices
from datetime import datetime
def _generate_captcha_text(length=5):
return ''.join(choices("ABCDEFGHJKLMNPQRSTUVWXYZ23456789", k=length))
# 生成验证码
def generate_captcha():
# 生成唯一ID作为验证码标识
captcha_id = f"{int(datetime.now().timestamp() * 1000)}{randint(1000, 9999)}"
captcha_text = _generate_captcha_text()
captcha_image = _generate_captcha_image(captcha_text)
return captcha_id,captcha_text,captcha_image
# 创建随机颜色
def _random_color():
"""
生成随机颜色
:return:
"""
return randint(150, 235), randint(150, 235), randint(150, 235)
def _generate_captcha_image(captcha_text):
image_width, image_height = 150, 40
font_size: int = 25
mode: str = 'RGB'
character_length = len(captcha_text)
# 创建一个白色背景的图像
image = Image.new(mode, (image_width, image_height), 'white')
draw = ImageDraw.Draw(image)
font = ImageFont.load_default(size=font_size)
# 绘制验证码文字
for i, char in enumerate(captcha_text):
x = 5 + i * (image_width-5)/(character_length)
y = randint(-5, 5)
draw.text((x, y), text=char, font=font, fill=_random_color())
# 添加干扰线
for _ in range(10):
start = (randint(0, image_width), randint(0, image_height))
end = (randint(0, image_width), randint(0, image_height))
draw.line([start, end], fill=_random_color(), width=1)
# 写干扰点
for _ in range(150):
draw.point([randint(0, image_width), randint(0, image_height)], fill=_random_color())
for _ in range(10):
# 写干扰圆圈
x = randint(0, image_width)
y = randint(0, image_height)
radius = randint(2, 4)
draw.arc((x-radius, y-radius, x + radius, y + radius), 0, 360, fill=_random_color())
return image
您也可以直接下载完整代码: gitee | github | gitcode
在fastAPI中生成和校验验证码
- 生成验证码
from util.captcha import generate_captcha
from util.ttlcache import Cache,Error
_cache = Cache(max_size=300, ttl=300) # 300个缓存,每个缓存5分钟
@app.get("/captcha")
def get_captcha():
if _cache.is_full():
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests")
captcha_id,captcha_text,captcha_image = generate_captcha()
print(f"生成的验证码: {captcha_id} {captcha_text}")
result = _cache.add(captcha_id,(captcha_text,captcha_image))
if result != Error.OK:
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests")
# 返回图片流
buffer = BytesIO()
captcha_image.save(buffer, format="PNG")
buffer.seek(0)
headers = {custom_header_name: captcha_id,"Cache-Control": "no-store"}
#print(headers)
return StreamingResponse(buffer, headers=headers, media_type="image/png")
生成验证码时使用了缓存,每个验证码缓存5分钟后自动清除。
这个缓存在 实现可以自动清除过期条目的缓存 中有介绍。
- 校验图片验证码 我们在登录接口中增加了参数:aptcha_id 和 captcha_input,用以接受客户端传来的验证码。
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(),remember: bool|None=Body(None),
captcha_id: str|None=Body(None), captcha_input: str|None=Body(None),log_details: None = Depends(log_request_details))-> Token:
'''
OAuth2PasswordRequestForm 是用以下几项内容声明表单请求体的类依赖项:
username
password
scope、grant_type、client_id等可选字段。
'''
# 校验验证码
error,value = _cache.get(captcha_id)
if error != Error.OK:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired captcha ID")
captcha_text = value[0]
if not captcha_text:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired captcha ID")
if captcha_text.upper() != captcha_input.upper():
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect captcha")
在服务端要合理设定 CORS
,允许跨域访问以及自定义header,否则客户端可能无法访问生成的图片验证码或者无法获取通过header携带的captcha ID,相关代码如下:
custom_header_name = "X-Captcha-ID"
# 允许跨域访问
from fastapi.middleware.cors import CORSMiddleware
origins = config["origins"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=[custom_header_name,"Cache-Control"], # 允许前端访问的头部,不如此设置客户端获取不到这些头信息
)
在客户端使用图片验证码
这里包含页面打开后自动获取验证码,以及点击图片时自动刷新验证码。
在请求验证码时,务必设定 responseType: “blob”,否则无法显示二维码。
显示验证码的图片控件:
<v-container>
<v-row>
<v-text-field
v-model="form_data.capchaText"
label="输入验证码"
variant="solo"
:rules="[rules.required, rules.max]"
></v-text-field
><v-img
:src="imageSrc"
alt="验证码"
class="mb-4"
max-height="60"
@click="refreshCaptcha"
style="cursor: pointer"
>
</v-img>
</v-row>
</v-container>
相关的 vuejs
脚本:
import { ref, onMounted } from "vue";
import axios from "axios";
const capcha_url = "http://127.0.0.1:8000/captcha";
const imageSrc = ref("");
//表单数据
const form_data = ref({
username: "",
password: "",
remember: false,
capchaId: "",
capchaText: "",
});
// 获取验证码
const fetchCaptcha = async () => {
try {
let img_url = capcha_url + "?t=" + Date.now();
const response = await axios(img_url, { responseType: "blob" }); // 响应类型为 blob,非常重要!
console.log("获取验证码成功:", response);
form_data.value.capchaId = response.headers["x-captcha-id"]; // 验证码唯一标识符
console.log("验证码ID:", form_data.value.capchaId);
imageSrc.value = URL.createObjectURL(response.data);
} catch (error) {
console.log("获取验证码失败:", error);
if (error.code == "ERR_NETWORK") {
error_msg.value = "网络错误,无法连接到服务器。";
} else {
error_msg.value = error.response.data.detail;
}
}
if (error_msg.value != "") {
error.value = true;
}
};
// 刷新验证码
const refreshCaptcha = () => {
fetchCaptcha();
form_data.value.capchaText = ""; // 清空用户输入
};
onMounted(() => {
fetchCaptcha();
});
总结
通过给登录功能增加图片验证码,可提升用户认证的安全性。
以上所述功能已经应用在 langchain+llama3+Chroma RAG demo 中,欢迎体验并指正。
以下是所有源代码的地址:
gitee | github | gitcode
🪐祝您好运🪐