给没有登录认证的web应用添加登录认证(openresty lua实现)
这阵子不是deepseek火么?我也折腾了下本地部署,ollama、vllm、llama.cpp都弄了下,webui也用了几个,发现nextjs-ollama-llm-ui小巧方便,挺适合个人使用的。如果放在网上供多人使用的话,得接入登录认证才好,不然所有人都能蹭玩,这个可不太妙。
我是用openresty反向代理将webui发布出去的,有好几种方案实现接入外部登录认证系统。首先是直接修改nextjs-ollama-llm-ui的源码,其实我就是这么做的,因为这样接入能将登录用户信息带入应用,可以定制页面,将用户显示在页面里,体验会更好。其次openresty是支持auth_request的,你需要编码实现几个web接口就可以了,进行简单配置即可,这种方式也很灵活,逻辑你自行编码实现。还有一种就是在openresty里使用lua来对接外部认证系统,也就是本文要介绍的内容。
在折腾的过程中,开始是想利用一些现有的轮子,结果因为偷懒反而踩了不少坑。包括但不限于openssl、session,后来一想,其实也没有多难,手搓也不复杂。
首先是这样设计的,用户的标识信息写入cookie,比如用一个叫做SID的字段,其构成为时间戳+IP,aes加密后的字符串;当用户的IP发生变化或者其他客户端伪造cookie访问,openresty可以识别出来,归类到未认证用户,跳转到认证服务器(带上回调url)。
location /webui {content_by_lua_block {local resty_string = require "resty.string"local resty_aes = require "resty.aes"local key = "1234567890123456" -- 16 bytes key for AES-128local iv = "1234567890123456" -- 16 bytes IV for AES-128local aes = resty_aes:new(key, nil, resty_aes.cipher(128, "cbc"), {iv=iv})local redis = require "resty.redis"local red = redis:new()red:set_timeouts(1000, 1000, 1000) -- 连接超时、发送超时、读取超时local ok, err = red:connect("127.0.0.1", 6379)if not ok thenngx.say("Failed to connect to Redis: ", err)returnendfunction get_client_ip()local headers = ngx.req.get_headers()local client_ip-- 优先从 X-Forwarded-For 获取local x_forwarded_for = headers["X-Forwarded-For"]if x_forwarded_for thenclient_ip = x_forwarded_for:match("([^,]+)")end-- 如果 X-Forwarded-For 不存在,尝试从 X-Real-IP 获取if not client_ip thenclient_ip = headers["X-Real-IP"]end-- 如果以上都不存在,回退到 remote_addrif not client_ip thenclient_ip = ngx.var.remote_addrendreturn client_ipendlocal function hex_to_bin(hex_str)-- 检查输入是否为有效的十六进制字符串if not hex_str or hex_str:len() % 2 ~= 0 thenreturn nil, "Invalid hex string: length must be even"endlocal bin_data = ""for i = 1, #hex_str, 2 do-- 每两个字符表示一个字节local byte_str = hex_str:sub(i, i + 1)-- 将十六进制字符转换为数字local byte = tonumber(byte_str, 16)if not byte thenreturn nil, "Invalid hex character: " .. byte_strend-- 将数字转换为对应的字符bin_data = bin_data .. string.char(byte)endreturn bin_dataendlocal cookies = ngx.var.http_Cookieif cookies thenlocal my_cookie = ngx.re.match(cookies, "sid=([^;]+)")if my_cookie thenlocal ckv=my_cookie[1]local ckvr=hex_to_bin(ckv)local decrypted = aes:decrypt(ckvr)local getip=string.sub(decrypted,12)if getip ~= get_client_ip() thenreturn ngx.exit(ngx.HTTP_BAD_REQUEST)endlocal userinfo, err = red:get(ckv)if not userinfo thenreturn ngx.redirect('https://sso.yourdomain.com/oauth2/authorize?redirect_uri=https://webapp.yourdomain.com/sso/callback')endelsereturn ngx.redirect('https://sso.yourdomain.com/oauth2/authorize?redirect_uri=https://webapp.yourdomain.com/sso/callback')end}proxy_pass http://127.0.0.1:3000;
}...
用户认证信息是存放在后端redis中,key是SID,value是认证访问返回的userid,在认证成功后写入,看是否需要在用户注销时主动删除记录。可以在nginx.conf里添加logout路径,但是可能需要在相关页面中放进去才好工作,否则用户估计不会在浏览器中手工输入logout的url来注销的。可以在cookie设置时设定有效时长,在redis添加记录时设置有效时长。
-- callback
location /callback {content_by_lua_block {local http = require "resty.http"-- 获取授权码local args = ngx.req.get_uri_args()local code = args.codelocal state = args.state-- 验证 stateif state ~= "some_random_state" thenngx.status = ngx.HTTP_BAD_REQUESTngx.say("Invalid state")return ngx.exit(ngx.HTTP_BAD_REQUEST)end-- 获取 access tokenlocal httpc = http.new()local res, err = httpc:request_uri("https://sso.yourdomain.com/oauth2/token", {method = "POST",body = ngx.encode_args({code = code,client_id = "YOUR_CLIENT_ID",client_secret = "YOUR_CLIENT_SECRET",grant_type = "authorization_code"}),headers = {["Content-Type"] = "application/x-www-form-urlencoded"}})if not res thenngx.status = ngx.HTTP_INTERNAL_SERVER_ERRORngx.say("Failed to request token: ", err)return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)endlocal token = res.body.access_token-- 获取用户信息local res, err = httpc:request_uri("https://sso.yourdomain.com/oauth2/v1/userinfo", {method = "GET",body = ngx.encode_args({client_id = "YOUR_CLIENT_ID",accesstoken = token}),})if not res thenngx.status = ngx.HTTP_INTERNAL_SERVER_ERRORngx.say("Failed to request user info: ", err)return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)endlocal user_info = res.bodylocal redis = require "resty.redis"local red = redis:new()red:set_timeouts(1000, 1000, 1000) -- 连接超时、发送超时、读取超时local ok, err = red:connect("127.0.0.1", 6379)if not ok thenngx.say("Failed to connect to Redis: ", err)returnendfunction get_client_ip()local headers = ngx.req.get_headers()local client_ip-- 优先从 X-Forwarded-For 获取local x_forwarded_for = headers["X-Forwarded-For"]if x_forwarded_for thenclient_ip = x_forwarded_for:match("([^,]+)")end-- 如果 X-Forwarded-For 不存在,尝试从 X-Real-IP 获取if not client_ip thenclient_ip = headers["X-Real-IP"]end-- 如果以上都不存在,回退到 remote_addrif not client_ip thenclient_ip = ngx.var.remote_addrendreturn client_ipendlocal timestamp = os.time()local text = timestamp ..":"..get_client_ip()local resty_string = require "resty.string"local resty_aes = require "resty.aes"local key = "1234567890123456" -- 16 bytes key for AES-128local iv = "1234567890123456" -- 16 bytes IV for AES-128local aes = resty_aes:new(key, nil, resty_aes.cipher(128, "cbc"), {iv=iv})local encrypted = aes:encrypt(text)local my_value=resty_string.to_hex(encrypted)ngx.header["Set-Cookie"] = "sid=" .. my_value .. "; Path=/; Expires=" .. ngx.cookie_time(ngx.time() + 14400) .. "; HttpOnly"local key = my_valuelocal value = user_info.useridlocal expire_time = 14400 -- 四小时后过期local res, err = red:set(key, value, "EX", expire_time)if not res thenngx.say("Failed to set key: ", err)returnend-- 重定向到受保护的页面ngx.redirect("/webui")}
}
其实也有现成的oauth2的轮子,不过我们自己手写lua代码的话,可以更灵活的配置,对接一些非标准的web认证服务也可以的。
相关文章:
给没有登录认证的web应用添加登录认证(openresty lua实现)
这阵子不是deepseek火么?我也折腾了下本地部署,ollama、vllm、llama.cpp都弄了下,webui也用了几个,发现nextjs-ollama-llm-ui小巧方便,挺适合个人使用的。如果放在网上供多人使用的话,得接入登录认证才好&a…...

3月5日作业
代码作业: #!/bin/bash# 清空目录函数 safe_clear_dir() {local dir"$1"local name"$2"if [ -d "$dir" ]; thenwhile true; doread -p "检测到 $name 目录已存在,请选择操作: 1) 清空目录内容 2) 保留目…...

【MySQL】增删改查
目录 一、新增(Create) 单行数据 全列插入 多行数据 指定列插入 插入时间 二、查询(Retrieve) 全列查询 指定列查询 查询字段为表达式 别名 去重:DISTINCT 排序:ORDER BY 条件查询࿱…...

【三维生成】StarGen:基于视频扩散模型的可扩展的时空自回归场景生成
标题:《StarGen: A Spatiotemporal Autoregression Framework with Video Diffusion Model for Scalable and Controllable Scene Generation》 项目:https://zju3dv.github.io/StarGen 来源:商汤科技、浙大CAD、Tetras.AI 文章目录 摘要一、…...

线反转法实现矩形键盘按键识别
由于行、列线为多键共用,各按键彼此将相互发 生影响,必须将行、列线信号配合起来并作适当的处 理,才能确定闭合键的位置。 线反转法 第1步:列线输出为全低电平,则行线中电平由高变低 的所在行为按键所在行。 第2步&…...

在 Element Plus 的 <el-select> 组件中,如果需要将 <el-option> 的默认值设置为 null。 用于枚举传值
文章目录 引言轻松实现 `<el-option>` 的默认值为 `null`I 实现方式监听清空事件 【推荐】使用 v-model 绑定 null添加一个值为 null 的选项处理 null 值的显示引言 背景:接口签名规则要求空串参与,空对象不参与签名计算 // 空字符串“” 参与签名组串,null不参与签…...
大白话面试中应对自我介绍
在面试中,自我介绍是开场的关键环节,它就像你递给面试官的一张“个人名片”,要让面试官快速了解你并对你产生兴趣。下面详细讲讲应对自我介绍的要点及回答范例。 一、自我介绍的时间把控 一般面试中的自我介绍控制在1 - 3分钟比较合适。时间…...
Pytorch构建LeNet进行MNIST识别 #自用
LeNet是一种经典的卷积神经网络(CNN)结构,由Yann LeCun等人在1998年提出,主要用于手写数字识别(如MNIST数据集)。作为最早的实用化卷积神经网络,LeNet为现代深度学习模型奠定了基础,…...

元宇宙崛起:区块链与金融科技共绘数字新世界
文章目录 一、引言二、元宇宙与区块链的深度融合三、区块链在元宇宙金融中的应用四、金融科技在元宇宙中的创新应用五、面临的挑战与机遇《区块链与金融科技》亮点内容简介获取方式 一、引言 随着科技的飞速发展,元宇宙概念逐渐走进人们的视野,成为数字…...

React Native 实现滑一点点内容区块指示器也滑一点点
效果图如上,内容滑一点点,指示器也按比例话一点点,列表宽度跟数据有关。 实现思路如下: 1.监听列表滑动事件,获取列表横向滑动距离,假设为A; 2.获取列表的宽度,及列表可滑动的宽度…...

怎么写C#命令行参数程序,及控制台带参数案例(程序完整源码)下载
C#命令行参数解析控制台带参数编写案例(程序完整源码)下载链接 https://download.csdn.net/download/luckyext/90434790 在CMD命令窗口,输入ping 、ipconfig等这样的命令,大家应该都知道,但很多同学可能不知道怎么写…...

全国青少年航天创新大赛各项目对比分析
全国青少年航天创新大赛各项目对比分析 一、比赛场地对比 项目名称场地尺寸场地特点组别差异筑梦天宫虚拟三维场景动态布局,小学组3停泊处,初高中组6停泊处;涉及传送带、机械臂、传感器等虚拟设备。初中/高中组任务复杂度更高,运…...

基于RAG的法律条文智能助手
文章目录 前言一、 项目背景与需求设计二、 数据收集与整理三、 核心实现流程1. 配置与模型初始化1. 配置区2. 模型初始化(init_models 函数)3. 数据加载与验证(load_and_validate_json_files 函数)4. 节点生成(create…...

智能对讲机:5G+AI赋能下的石油工业新“声”态
在浩瀚的能源版图上,中国正以非凡的“深度”探索着石油资源的奥秘。随着5G技术的不断成熟与普及,曾经“满山遍野找信号”的石油工人,如今已步入了一个全新的通信时代。在这个时代里,智能对讲机成为了连接指挥中心与一线工人的桥梁…...

leetcode日记(77)子集Ⅱ
不知道为什么看到这道题就很头痛…… 其实只要掌握nums不包含重复元素的情况下的代码就行了。 若nums不能包含重复元素,那么使用回溯很容易就能写出来: class Solution {void hs(vector<int> v,int x,vector<int> r,vector<vector<…...
Linux tar命令
压缩解压缩 1. tar 命令 语法: tar [主选项 辅选项] 文件或目录 参数功能-c创建新的归档文件(打包)-x从归档文件中提取文件(解包)-f <文件名>指定归档文件名-v显示操作的详细信息-z通过gzip压缩归档文件-j通…...
【nodeJS】服务端连接mysql、定义一个接口,并在前端调用
服务端连接数据库,并简单使用 服务器连接mysql后端定义接口前端调用接口封装axios(简易版)解决前端请求接口返回了一个html 定义api请求vue中调用接口 服务器连接mysql 安装mysql2:npm install mysql2启动服务:npm sta…...
驱动开发系列40 - Linux 显卡KMD驱动代码分析(一) - 设备初始化过程
目录 一:概述 二:显卡内核态驱动的主要功能 1. 设备初始化 2. 内存管理 3. 中断处理 4. 显示管理 5. 电源管理 三:Linux显卡内核态驱动的架构 四:PCI设备初始化过程 五:显卡设备初始化 一:概述 显卡内核态驱动(KMD)负责与GPU硬件直接交互,提供底层接口、管理显存…...

玩转大语言模型——Ubuntu系统环境下使用llama.cpp进行CPU与GPU混合推理deepseek
系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——三分钟教你用langchain提示词工程获得猫娘女友 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型—…...

20250301在chrome中安装CRX猫抓
20250301在chrome中安装CRX猫抓 2025/3/1 18:08 百度:猫抓 CRX https://www.crx4chrome.com/crx/49597/ 猫抓 (cat-catch) Crx File 2.5.9 for Chrome (Latest Version) Get Latest Version of 猫抓 (cat-catch) from Web Store Developer Tools > cat-catch / E…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...