368 lines
13 KiB
Lua
368 lines
13 KiB
Lua
---
|
||
--- Generated by EmmyLua(https://github.com/EmmyLua)
|
||
--- Created by admin.
|
||
--- DateTime: 2025/10/28 11:09
|
||
--- 用于
|
||
local resp = require("util.response")
|
||
local oauthDao = require("dao.oauth.oauth")
|
||
local validator = require("validator.oauth.oauth")
|
||
local cjson = require("cjson.safe")
|
||
local jwt = require "resty.jwt"
|
||
local rsa = require("util.rsa")
|
||
local authcode = require("util.authcode")
|
||
local token = require("util.token")
|
||
local client = require("util.client")
|
||
local conf = require("config")
|
||
local red = require("share.redis")
|
||
|
||
local _M = {}
|
||
|
||
--获取授权码
|
||
function _M:authorize()
|
||
local args = ngx.req.get_uri_args()
|
||
if ngx.req.get_method() == "POST" then
|
||
-- 读取请求体的数据
|
||
ngx.req.read_body()
|
||
-- 获取请求数据
|
||
local body_data = ngx.req.get_body_data()
|
||
-- 验证json数据是否正确
|
||
local ok, data = pcall(cjson.decode, body_data)
|
||
if not ok then
|
||
return ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
-- 校验客户端请求参数
|
||
ok = validator.validateAuthorize(data)
|
||
--验证失败则返回
|
||
if not ok then
|
||
return ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
end
|
||
-- 校验 response_type 必须为 "code"(授权码模式)
|
||
if args.response_type ~= "code" then
|
||
return ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
-- 1、校验客户端id和redirect_uri是否存在数据库
|
||
local client_id = args.client_id
|
||
local redirect_uri = args.redirect_uri
|
||
local code, res = oauthDao.getApplicationBy(client_id, redirect_uri)
|
||
if code ~= 0 or not res then
|
||
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
-- 2、验证范围
|
||
if args.scope then
|
||
local requested_scopes = {}
|
||
for scope in string.gmatch(args.scope, "%S+") do
|
||
table.insert(requested_scopes, scope)
|
||
end
|
||
-- 验证范围是否允许 todo
|
||
end
|
||
-- 3、判断用户登录检查 用户已登录,直接展示授权确认页;未登录则重定向到登录页
|
||
local user, err = client.validate(client_id, redirect_uri)
|
||
if user == nil then
|
||
-- 重定向到登录页,携带当前授权请求参数(登录后跳转回来)
|
||
local login_url = "/login?redirect=" .. ngx.escape_uri(ngx.var.request_uri)
|
||
--print("authorize login_url:", login_url)
|
||
--ngx.redirect(login_url)
|
||
local result = resp:json(ngx.HTTP_MOVED_TEMPORARILY, login_url)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 4. 生成授权码(随机字符串,确保唯一性)(用户ID、客户端ID、scope、生成时间)
|
||
local auth_code, err = authcode.create(user.userid, client_id, redirect_uri, args.scope)
|
||
if not auth_code then
|
||
ngx.log(ngx.ERR, "生成授权码失败: ", err)
|
||
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||
end
|
||
--print("token set shared dict key:",code_key)
|
||
-- 5. 重定向到客户端回调地址,携带授权码和原始 state(防 CSRF)
|
||
local redirect_url = args.redirect_uri .. "?code=" .. code .. "&state=" .. args.state
|
||
local rest = {}
|
||
rest.redirect_uri = redirect_uri
|
||
rest.code = auth_code
|
||
rest.state = args.state
|
||
local result = resp:json(ngx.HTTP_OK, rest)
|
||
resp:send(result)
|
||
end
|
||
|
||
-- 通过用户名认证用户和应用是否存在状态
|
||
local function authorizatePassword(args)
|
||
-- 1.校验必填参数验证数据是否符合json
|
||
local ok = validator.validateLogin(args)
|
||
if not ok then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 2.验证用户名和密码,应用程序id和应用程序密钥
|
||
local code, res = oauthDao.authenticateUserPasswd(args.username, args.password)
|
||
if code ~= 0 or res == nil then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
print("验证用户名和密码: ", args.username)
|
||
-- 3.对当前用户的持有的应用程序进行验证
|
||
local userid = res[1].id
|
||
local client_id = args.client_id
|
||
local client_secret = args.client_secret
|
||
code, res = oauthDao.getApplicationByUserid(userid, client_id, client_secret)
|
||
if code ~= 0 or res == nil then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
local redirect_uri = res[1].redirect_uris
|
||
-- 4.生成授权码(随机字符串,确保唯一性)(用户ID、客户端ID、scope、生成时间)
|
||
local auth_code, err = authcode.create(userid, client_id, redirect_uri)
|
||
if not auth_code then
|
||
ngx.log(ngx.ERR, "生成授权码失败: ", err)
|
||
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||
end
|
||
-- 5.存储用户信息,证明用户已经登陆
|
||
client.create(userid, client_id, redirect_uri)
|
||
-- 6.返回结果
|
||
local rest = {}
|
||
rest.redirect_uri = redirect_uri
|
||
rest.code = auth_code
|
||
local result = resp:json(ngx.HTTP_OK, rest)
|
||
resp:send(result)
|
||
end
|
||
|
||
-- 通过code形式进行认证
|
||
local function authorizateCode(args)
|
||
-- 1.校验必填参数验证数据是否符合json
|
||
local ok = validator.validateToken(args)
|
||
if not ok then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 2.校验 code 有效性
|
||
local code_data, err = authcode.consume(args.code)--, args.client_id)
|
||
if not code_data then
|
||
ngx.log(ngx.ERR, "授权码验证失败: ", err)
|
||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
-- 3.验证redirect_url地址的正确性
|
||
local request_uri = code_data.redirect_uri
|
||
print("token request_uri:", request_uri)
|
||
if request_uri ~= args.redirect_uri then
|
||
print("token redirect_url:", request_uri, args.redirect_uri)
|
||
local login_url = "/login?redirect=" .. ngx.escape_uri(request_uri)
|
||
local result = resp:json(ngx.HTTP_MOVED_TEMPORARILY, login_url)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 4.生成密钥对
|
||
--local pub_key, priv_key, err = rsa.generate_rsa_keys(2048)
|
||
--if err then
|
||
-- print("密钥生成失败: ", err)
|
||
-- local result = resp:json(0x00001)
|
||
-- resp:send(result)
|
||
-- return
|
||
--end
|
||
--print("token pubkey:", pub_key)
|
||
local priv_key = conf.secret_key
|
||
local user_id = code_data.user_id
|
||
local client_id = code_data.client_id
|
||
local scope = code_data.scope
|
||
print("authorizateCode user_id:", user_id, " client_id:", client_id)
|
||
-- 5.生成新 Access Token
|
||
local new_access_token = token.generate_access_token(priv_key, user_id, client_id, scope)
|
||
-- 生成新 Refresh Token(滚动刷新)
|
||
local new_refresh_token = token.generate_refresh_token(priv_key, user_id, client_id, scope)
|
||
--创建用户和应用id的刷新token
|
||
client.createRefreshToken(user_id, client_id, new_refresh_token)
|
||
-- 生存id_token
|
||
local new_id_token = token.generate_id_token(priv_key, user_id, client_id, scope)
|
||
--ngx.say("Generated JWT: ", jwt_obj)
|
||
local ret = {}
|
||
ret.access_token = new_access_token
|
||
ret.token_type = "Bearer"
|
||
ret.expires_in = 10 * 60
|
||
ret.refresh_token = new_refresh_token
|
||
ret.id_token = new_id_token
|
||
-- 6.将生成的数据存储到数据库中
|
||
local code, res = oauthDao.updateApplicationToken(client_id, ret)
|
||
if code ~= 0 then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 7.返回结果
|
||
local result = resp:json(ngx.HTTP_OK, ret)
|
||
resp:send(result)
|
||
end
|
||
|
||
-- 刷新令牌
|
||
local function authorizateRefresh(args)
|
||
-- 1.校验必填参数验证数据是否符合json
|
||
local res = validator.validateRefresh(args)
|
||
if not res then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 2.验证并消费 refresh_token(滚动刷新:生成新的 rt)
|
||
--print("begin consume token:", args.client_id)
|
||
local rt_data, err = client.consumeRefreshToken(args.refresh_token, args.client_id, true)
|
||
if not rt_data then
|
||
ngx.log(ngx.ERR, "refresh_token 验证失败: ", err)
|
||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
-- 3.生成新 Access Token
|
||
--print("begin generate token:", rt_data.scope, args.client_id)
|
||
local priv_key = conf.secret_key
|
||
local new_access_token = token.generate_access_token(priv_key, rt_data.sub, args.client_id, rt_data.scope)
|
||
--存储到redis中,并设置有效期时间
|
||
rt_data.revoked = true
|
||
red:set("oidc:refresh_token:"..new_access_token, cjson.encode(rt_data))
|
||
red:expire("oidc:refresh_token:"..new_access_token, conf.refresh_token_ttl)
|
||
-- 生存id_token
|
||
local new_id_token = token.generate_id_token(priv_key, rt_data.sub, args.client_id, rt_data.scope)
|
||
--ngx.say("Generated JWT: ", jwt_obj)
|
||
local ret = {}
|
||
ret.access_token = new_access_token
|
||
ret.token_type = "Bearer"
|
||
ret.expires_in = 10 * 60
|
||
ret.id_token = new_id_token
|
||
-- 4.返回结果
|
||
local result = resp:json(ngx.HTTP_OK, ret)
|
||
resp:send(result)
|
||
end
|
||
|
||
-- 根据授权码获取Access-Token
|
||
function _M:token()
|
||
-- 1. 解析请求参数(支持 form-data 和 json)
|
||
local content_type = ngx.req.get_headers()["Content-Type"] or ""
|
||
local args = {}
|
||
--print("token content_type:", content_type)
|
||
if string.find(content_type, "application/json") then
|
||
-- 读取请求体的数据
|
||
ngx.req.read_body()
|
||
-- 获取请求数据
|
||
local body_data = ngx.req.get_body_data()
|
||
if not body_data then
|
||
return ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
-- 验证json数据是否正确
|
||
local ok, data = pcall(cjson.decode, body_data)
|
||
if not ok then
|
||
return ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
args = data
|
||
else
|
||
if ngx.req.get_method() == "POST" then
|
||
-- 默认解析 form-urlencoded
|
||
args = ngx.req.get_post_args()
|
||
elseif ngx.req.get_method() == "GET" then
|
||
args = ngx.req.get_uri_args()
|
||
end
|
||
end
|
||
|
||
local grant_type = args.grant_type
|
||
--print("grant_type类型: ", grant_type)
|
||
if grant_type == "password" then
|
||
authorizatePassword(args)
|
||
elseif grant_type == "authorization_code" then
|
||
authorizateCode(args)
|
||
elseif grant_type == "refresh_token" then
|
||
authorizateRefresh(args)
|
||
else
|
||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||
end
|
||
end
|
||
|
||
--根据Access-Token获取相应用户的账户信息
|
||
function _M:userinfo()
|
||
--获取用户认证数据信息
|
||
local auth_header = ngx.var.http_Authorization
|
||
|
||
-- 1.如果请求头中没有令牌,则直接返回401
|
||
if auth_header == nil or auth_header == "" then
|
||
ngx.log(ngx.WARN, "没有找到令牌数据")
|
||
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
-- 2.查找令牌中的Bearer前缀字符
|
||
local data = {}
|
||
data.Authorization = auth_header
|
||
local ok = validator.validateUserinfo(data)
|
||
if not ok then
|
||
ngx.log(ngx.WARN, "令牌格式不正确")
|
||
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
-- 3.获取token的数据值
|
||
local token = string.sub(auth_header,8)
|
||
--校验令牌
|
||
--local pub_key, priv_key, err = rsa.generate_rsa_keys(2048)
|
||
--if err then
|
||
-- --print("密钥生成失败: ", err)
|
||
-- local result = resp:json(0x00001)
|
||
-- resp:send(result)
|
||
-- return
|
||
--end
|
||
-- 4.对token进行验证
|
||
--print("userinfo pubkey:", pub_key)
|
||
local pub_key = conf.secret_key
|
||
local jwt_obj = jwt:verify(pub_key, token)
|
||
--如果校验结果中的verified==false,则表示令牌无效
|
||
if jwt_obj.verified == false then
|
||
ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
|
||
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
|
||
--判断token是否超时 --令牌已过期
|
||
if jwt_obj.payload.exp and ngx.time() > jwt_obj.payload.exp then
|
||
ngx.log(ngx.WARN, "token timeout ".. jwt_obj.reason)
|
||
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
--通过用户id获取用户信息
|
||
--print("-- get jwt_obj.payload value --")
|
||
--for key, value in pairs(jwt_obj.payload) do
|
||
-- print("jwt_obj.payload: ", key, " ", value)
|
||
--end
|
||
local user_id = jwt_obj.payload.sub
|
||
local code, rest = oauthDao.getUser(user_id)
|
||
--读取数据错误
|
||
if code ~= 0 or rest == nil then
|
||
local result = resp:json(0x000001)
|
||
resp:send(result)
|
||
return
|
||
end
|
||
-- 5.获取token中的信息进行所需用户的信息返回
|
||
local ret = {}
|
||
ret.sub = user_id
|
||
ret.name = rest[1].username
|
||
ret.phone = rest[1].phone
|
||
ret.real_name = rest[1].realname
|
||
ret.office_phone = rest[1].office_phone
|
||
ret.email = rest[1].email
|
||
local result = resp:json(ngx.HTTP_OK, ret)
|
||
resp:send(result)
|
||
end
|
||
|
||
--回收token
|
||
function _M:logout()
|
||
-- 假设从会话中获取用户 ID(sub)
|
||
local sub = "user123" -- 实际应从登录会话中获取
|
||
|
||
-- 遍历所有 refresh_token(生产环境建议用 Redis 哈希存储用户与 rt 的映射)
|
||
local keys = red:keys("oidc:refresh_token:*")
|
||
for _, key in ipairs(keys) do
|
||
local rt_data_str = red:get(key)
|
||
if rt_data_str and rt_data_str ~= ngx.null then
|
||
local rt_data = cjson.decode(rt_data_str)
|
||
if rt_data.sub == sub then
|
||
rt_data.revoked = true
|
||
red:set(key, cjson.encode(rt_data))
|
||
red:expire(key, 0) -- 立即过期
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return _M |