给没有登录认证的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…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
