AuthPlatform/src/service/oauth/oauth.lua
2025-11-16 23:29:46 +08:00

368 lines
13 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
--- 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()
-- 假设从会话中获取用户 IDsub
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