Compare commits
3 Commits
68337dc605
...
e75cbc7282
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e75cbc7282 | ||
|
|
2d8459e093 | ||
|
|
5095f35e12 |
|
|
@ -34,7 +34,7 @@ local routes = {
|
||||||
--回收token
|
--回收token
|
||||||
{
|
{
|
||||||
paths = { "/yum/v1/oauth/v2/logout" },
|
paths = { "/yum/v1/oauth/v2/logout" },
|
||||||
methods = { "POST" },
|
methods = { "GET", "POST" },
|
||||||
handler = oauthService.logout,
|
handler = oauthService.logout,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ local authcode = require("util.authcode")
|
||||||
local token = require("util.token")
|
local token = require("util.token")
|
||||||
local client = require("util.client")
|
local client = require("util.client")
|
||||||
local conf = require("config")
|
local conf = require("config")
|
||||||
|
local red = require("share.redis")
|
||||||
|
|
||||||
local _M = {}
|
local _M = {}
|
||||||
|
|
||||||
|
|
@ -99,6 +100,7 @@ local function authorizatePassword(args)
|
||||||
resp:send(result)
|
resp:send(result)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
print("验证用户名和密码: ", args.username)
|
||||||
-- 3.对当前用户的持有的应用程序进行验证
|
-- 3.对当前用户的持有的应用程序进行验证
|
||||||
local userid = res[1].id
|
local userid = res[1].id
|
||||||
local client_id = args.client_id
|
local client_id = args.client_id
|
||||||
|
|
@ -170,14 +172,14 @@ local function authorizateCode(args)
|
||||||
-- 生成新 Refresh Token(滚动刷新)
|
-- 生成新 Refresh Token(滚动刷新)
|
||||||
local new_refresh_token = token.generate_refresh_token(priv_key, user_id, client_id, scope)
|
local new_refresh_token = token.generate_refresh_token(priv_key, user_id, client_id, scope)
|
||||||
--创建用户和应用id的刷新token
|
--创建用户和应用id的刷新token
|
||||||
client.createRefreshToken(user_id, client_id)
|
client.createRefreshToken(user_id, client_id, new_refresh_token)
|
||||||
-- 生存id_token
|
-- 生存id_token
|
||||||
local new_id_token = token.generate_id_token(priv_key, user_id, client_id, scope)
|
local new_id_token = token.generate_id_token(priv_key, user_id, client_id, scope)
|
||||||
--ngx.say("Generated JWT: ", jwt_obj)
|
--ngx.say("Generated JWT: ", jwt_obj)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
ret.access_token = new_access_token
|
ret.access_token = new_access_token
|
||||||
ret.token_type = "Bearer"
|
ret.token_type = "Bearer"
|
||||||
ret.expires_in = 10 * 60
|
ret.expires_in = conf.access_token_ttl
|
||||||
ret.refresh_token = new_refresh_token
|
ret.refresh_token = new_refresh_token
|
||||||
ret.id_token = new_id_token
|
ret.id_token = new_id_token
|
||||||
-- 6.将生成的数据存储到数据库中
|
-- 6.将生成的数据存储到数据库中
|
||||||
|
|
@ -202,27 +204,27 @@ local function authorizateRefresh(args)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- 2.验证并消费 refresh_token(滚动刷新:生成新的 rt)
|
-- 2.验证并消费 refresh_token(滚动刷新:生成新的 rt)
|
||||||
local rt_data, err = client.consumeRefreshToken(res.refresh_token, res.client_id, true)
|
--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
|
if not rt_data then
|
||||||
ngx.log(ngx.ERR, "refresh_token 验证失败: ", err)
|
ngx.log(ngx.ERR, "refresh_token 验证失败: ", err)
|
||||||
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 生成新的 access_token 和 id_token
|
|
||||||
local userinfo = {
|
|
||||||
sub = rt_data.sub,
|
|
||||||
name = "Test User",
|
|
||||||
email = "test@example.com"
|
|
||||||
}
|
|
||||||
-- 3.生成新 Access Token
|
-- 3.生成新 Access Token
|
||||||
local new_access_token = token.generate_access_token(priv_key, user_id, client_id, scope)
|
--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
|
-- 生存id_token
|
||||||
local new_id_token = token.generate_id_token(priv_key, user_id, client_id, scope)
|
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)
|
--ngx.say("Generated JWT: ", jwt_obj)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
ret.access_token = new_access_token
|
ret.access_token = new_access_token
|
||||||
ret.token_type = "Bearer"
|
ret.token_type = "Bearer"
|
||||||
ret.expires_in = 10 * 60
|
ret.expires_in = conf.access_token_ttl
|
||||||
ret.id_token = new_id_token
|
ret.id_token = new_id_token
|
||||||
-- 4.返回结果
|
-- 4.返回结果
|
||||||
local result = resp:json(ngx.HTTP_OK, ret)
|
local result = resp:json(ngx.HTTP_OK, ret)
|
||||||
|
|
@ -259,6 +261,7 @@ function _M:token()
|
||||||
end
|
end
|
||||||
|
|
||||||
local grant_type = args.grant_type
|
local grant_type = args.grant_type
|
||||||
|
--print("grant_type类型: ", grant_type)
|
||||||
if grant_type == "password" then
|
if grant_type == "password" then
|
||||||
authorizatePassword(args)
|
authorizatePassword(args)
|
||||||
elseif grant_type == "authorization_code" then
|
elseif grant_type == "authorization_code" then
|
||||||
|
|
@ -344,11 +347,80 @@ end
|
||||||
|
|
||||||
--回收token
|
--回收token
|
||||||
function _M:logout()
|
function _M:logout()
|
||||||
-- 假设从会话中获取用户 ID(sub)
|
-- 1. 解析请求参数(支持 form-data 和 json)
|
||||||
local sub = "user123" -- 实际应从登录会话中获取
|
local content_type = ngx.req.get_headers()["Content-Type"] or ""
|
||||||
|
local args = {}
|
||||||
|
print("logout 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
|
||||||
|
-- 1、校验客户端id和redirect_uri是否存在数据库
|
||||||
|
local ok = validator.validateLogout(args)
|
||||||
|
if not ok then
|
||||||
|
print("validateLogout:", args)
|
||||||
|
local result = resp:json(0x000001)
|
||||||
|
resp:send(result)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local token = args.access_token
|
||||||
|
print("logout token:", 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.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 sub = jwt_obj.payload.sub
|
||||||
-- 遍历所有 refresh_token(生产环境建议用 Redis 哈希存储用户与 rt 的映射)
|
-- 遍历所有 refresh_token(生产环境建议用 Redis 哈希存储用户与 rt 的映射)
|
||||||
local keys = red:keys("oidc:refresh_token:*")
|
--获取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
|
for _, key in ipairs(keys) do
|
||||||
local rt_data_str = red:get(key)
|
local rt_data_str = red:get(key)
|
||||||
if rt_data_str and rt_data_str ~= ngx.null then
|
if rt_data_str and rt_data_str ~= ngx.null then
|
||||||
|
|
@ -360,6 +432,10 @@ function _M:logout()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- 5.获取token中的信息进行所需用户的信息返回
|
||||||
|
local ret = {}
|
||||||
|
local result = resp:json(ngx.HTTP_OK, ret)
|
||||||
|
resp:send(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
@ -11,7 +11,7 @@ local conf = require("config")
|
||||||
local _M = {}
|
local _M = {}
|
||||||
|
|
||||||
-- 客户端登录进行存储
|
-- 客户端登录进行存储
|
||||||
function _M:create(userid, client_id, redirect_uris)
|
function _M.create(userid, client_id, redirect_uris)
|
||||||
local client_str = {}
|
local client_str = {}
|
||||||
client_str.userid = userid
|
client_str.userid = userid
|
||||||
client_str.client_id = client_id
|
client_str.client_id = client_id
|
||||||
|
|
@ -42,18 +42,9 @@ function _M.validate(client_id, redirect_uri)
|
||||||
return client
|
return client
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 生成随机 refresh_token(64字节,更长更安全)
|
|
||||||
local function generate_refresh_token()
|
|
||||||
local random_bytes = random.bytes(64, true) -- 强随机数
|
|
||||||
return str.to_hex(random_bytes)
|
|
||||||
end
|
|
||||||
|
|
||||||
local str = require "resty.string"
|
|
||||||
local random = require "resty.random"
|
|
||||||
|
|
||||||
-- 存储 refresh_token(关联用户、客户端、过期时间)
|
-- 存储 refresh_token(关联用户、客户端、过期时间)
|
||||||
function _M.createRefreshToken(sub, client_id)
|
function _M.createRefreshToken(sub, client_id, refresh_token)
|
||||||
local rt = generate_refresh_token()
|
local rt = refresh_token
|
||||||
local rt_data = {
|
local rt_data = {
|
||||||
sub = sub, -- 用户唯一标识
|
sub = sub, -- 用户唯一标识
|
||||||
client_id = client_id, -- 客户端ID
|
client_id = client_id, -- 客户端ID
|
||||||
|
|
@ -62,7 +53,7 @@ function _M.createRefreshToken(sub, client_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 存储在 Redis:key=oidc:refresh_token:{rt}
|
-- 存储在 Redis:key=oidc:refresh_token:{rt}
|
||||||
local ok, err = red:set("oidc:refresh_token:" .. rt, cjson.encode(rt_data))
|
local ok, err = red:set("oidc:refresh_token:"..rt, cjson.encode(rt_data))
|
||||||
if not ok then
|
if not ok then
|
||||||
return nil, err
|
return nil, err
|
||||||
end
|
end
|
||||||
|
|
@ -95,24 +86,10 @@ function _M.consumeRefreshToken(rt, client_id, rolling)
|
||||||
if rt_data.revoked then
|
if rt_data.revoked then
|
||||||
return nil, "refresh_token 已吊销"
|
return nil, "refresh_token 已吊销"
|
||||||
end
|
end
|
||||||
|
-- 吊销旧的 refresh_token
|
||||||
|
red:expire("oidc:refresh_token:"..rt, 0) -- 立即过期
|
||||||
|
|
||||||
-- 滚动刷新:生成新的 refresh_token,吊销旧的
|
return rt_data
|
||||||
local new_rt = nil
|
|
||||||
if rolling then
|
|
||||||
new_rt, err = _M.create(rt_data.sub, client_id)
|
|
||||||
if not new_rt then
|
|
||||||
return nil, "生成新 refresh_token 失败: " .. (err or "")
|
|
||||||
end
|
|
||||||
-- 吊销旧的 refresh_token
|
|
||||||
rt_data.revoked = true
|
|
||||||
red:set("oidc:refresh_token:" .. rt, cjson.encode(rt_data))
|
|
||||||
red:expire("oidc:refresh_token:" .. rt, 0) -- 立即过期
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
sub = rt_data.sub,
|
|
||||||
new_refresh_token = new_rt -- 新的 refresh_token(若滚动刷新)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
@ -102,4 +102,22 @@ function _M.validateRefresh(jsonData)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local schemaLogout = {
|
||||||
|
type = "object",
|
||||||
|
properties = {
|
||||||
|
access_token = { type = "string" },
|
||||||
|
client_id = { type = "string" },
|
||||||
|
client_secret = { type = "string" },
|
||||||
|
},
|
||||||
|
required = { "access_token", "client_id", "client_secret" }
|
||||||
|
}
|
||||||
|
|
||||||
|
--回收Token
|
||||||
|
function _M.validateLogout(jsonData)
|
||||||
|
-- 验证数据是否符合schema
|
||||||
|
local validator = jsonschema.generate_validator(schemaLogout)
|
||||||
|
local result = validator(jsonData)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
Loading…
Reference in New Issue
Block a user