--- --- Generated by EmmyLua(https://github.com/EmmyLua) --- Created by admin. --- DateTime: 2025/10/28 11:09 --- 用于 local status = require("util.status") 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 = {} --获取uri中所携带的参数信息 local function getUriArgs() 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 args = data elseif ngx.req.get_method() == "GET" then args = ngx.req.get_uri_args() end return args end --获取授权码 function _M:authorize() local args = getUriArgs() -- 校验客户端请求参数 local ok = validator.validateAuthorize(args) --验证失败则返回 if not ok then return ngx.exit(ngx.HTTP_BAD_REQUEST) 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) if code ~= 0 or not res then return ngx.exit(ngx.HTTP_UNAUTHORIZED) end --判断redirect_uri是否在重定向组里面 local redirect_uris = res[1].redirect_uri -- 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) resp:response(ngx.HTTP_MOVED_TEMPORARILY, login_url) 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 resp:response(status.SUCCESS, rest) end -- 通过用户名认证用户和应用是否存在状态 local function authorizatePassword(args) -- 1.校验必填参数验证数据是否符合json local ok = validator.validateUserPasswd(args) if not ok then resp:response(status.PARAM_TYPE_BIND_ERROR) return end -- 2.验证用户名和密码,应用程序id和应用程序密钥 local code, res = oauthDao.authenticateUserPasswd(args.username, args.password) if code ~= 0 or res == nil then resp:response(status.USER_LOGIN_ERROR) 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 resp:response(status.DATA_NONE_FOUNT) return end local redirect_uri = res[1].redirect_uri -- 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 resp:response(status.SUCCESS, rest) end -- 通过code形式进行认证 local function authorizateCode(args) -- 1.校验必填参数验证数据是否符合json local ok = validator.validateToken(args) if not ok then resp:response(status.PARAM_IS_INVALID) return end print("consume code:", args.code) -- 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) resp:response(status.PARAM_IS_INVALID, login_url) return end -- 4.生成密钥对 --local pub_key, priv_key, err = rsa.generate_rsa_keys(2048) --if err then -- print("密钥生成失败: ", err) -- resp:response(0x00001) -- 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 = conf.access_token_ttl 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 resp:response(status.DATA_IS_WRONG) return end -- 7.返回结果 resp:response(status.SUCCESS, ret) end -- 刷新令牌 local function authorizateRefresh(args) -- 1.校验必填参数验证数据是否符合json local res = validator.validateRefresh(args) if not res then resp:response(status.PARAM_NOT_COMPLETE) 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 = conf.access_token_ttl ret.id_token = new_id_token -- 4.返回结果 resp:response(status.SUCCESS, ret) end -- 根据授权码获取Access-Token function _M:token() -- 1. 解析请求参数(支持 form-data 和 json) local args = getUriArgs() if args == nil then ngx.exit(ngx.HTTP_BAD_REQUEST) 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.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.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) -- resp:response(0x00001) -- 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获取用户信息 local user_id = jwt_obj.payload.sub local code, rest = oauthDao.getUser(user_id) --读取数据错误 if code ~= 0 or rest == nil then resp:response(status.DATA_NONE_FOUNT) 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 resp:response(status.SUCCESS, ret) end --回收token function _M:logout() -- 1. 解析请求参数(支持 form-data 和 json) local args = getUriArgs() -- 1、校验客户端id和redirect_uri是否存在数据库 local ok = validator.validateLogout(args) if not ok then print("validateLogout:", args) resp:response(status.PARAM_IS_INVALID) return end local token = args.access_token -- 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.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.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 sub = jwt_obj.payload.sub -- 遍历所有 refresh_token(生产环境建议用 Redis 哈希存储用户与 rt 的映射) --获取redis中所有匹配的数据内容 local pattern = "oidc:refresh_token:*" local cursor = "0" local keys = {} repeat local result, err = red:scan(cursor, 'MATCH', pattern) if not result then break end cursor = result[1] for _, key in ipairs(result[2]) do table.insert(keys, key) end until cursor == "0" 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 -- 5.获取token中的信息进行所需用户的信息返回 resp:response(status.SUCCESS) end return _M