将token时间增加到配置文件,增加回收token接口,修改刷新接口,并完善部分业务逻辑,修改refresh接口的验证
This commit is contained in:
parent
30fdac7cf8
commit
68337dc605
|
|
@ -75,9 +75,11 @@ http {
|
||||||
token_endpoint = "http://localhost:9080yum/v1/oauth/v2/token",
|
token_endpoint = "http://localhost:9080yum/v1/oauth/v2/token",
|
||||||
userinfo_endpoint = "http://localhost:9080yum/v1/oauth/v2/userinfo",
|
userinfo_endpoint = "http://localhost:9080yum/v1/oauth/v2/userinfo",
|
||||||
--jwks_uri = "http://localhost:9080/jwks", -- 公钥端点(可选)
|
--jwks_uri = "http://localhost:9080/jwks", -- 公钥端点(可选)
|
||||||
response_types_supported = {"code"},
|
grant_types_supported = [ "authorization_code", "token", "refresh_token" ], -- 新增支持 refresh_token
|
||||||
subject_types_supported = {"public"},
|
response_types_supported = { "code" },
|
||||||
id_token_signing_alg_values_supported = {"HS256"}
|
subject_types_supported = { "public" },
|
||||||
|
id_token_signing_alg_values_supported = { "HS256" },
|
||||||
|
refresh_token_issuance_supported = true -- 声明支持颁发 refresh_token
|
||||||
}
|
}
|
||||||
ngx.header["Content-Type"] = "application/json"
|
ngx.header["Content-Type"] = "application/json"
|
||||||
ngx.say(cjson.encode(config))
|
ngx.say(cjson.encode(config))
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ local routes = {
|
||||||
methods = { "POST" },
|
methods = { "POST" },
|
||||||
handler = oauthService.userinfo,
|
handler = oauthService.userinfo,
|
||||||
},
|
},
|
||||||
|
--回收token
|
||||||
|
{
|
||||||
|
paths = { "/yum/v1/oauth/v2/logout" },
|
||||||
|
methods = { "POST" },
|
||||||
|
handler = oauthService.logout,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 初始化路由
|
-- 初始化路由
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ local _M = {
|
||||||
|
|
||||||
secret_key = "!@#$5412$#@!", -- 确保这个密钥足够安全并保密
|
secret_key = "!@#$5412$#@!", -- 确保这个密钥足够安全并保密
|
||||||
|
|
||||||
|
access_token_ttl = 10 * 60, --十分钟
|
||||||
|
refresh_token_ttl = 7 * 24 * 3600, --7天
|
||||||
|
id_token_ttl = 60 * 60, --1小时
|
||||||
|
|
||||||
REDIS_PREFIX = 'Auth:',
|
REDIS_PREFIX = 'Auth:',
|
||||||
-- 配置redis数据库连接
|
-- 配置redis数据库连接
|
||||||
REDIS = {
|
REDIS = {
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ local function authorizateCode(args)
|
||||||
local new_access_token = token.generate_access_token(priv_key, user_id, client_id, scope)
|
local new_access_token = token.generate_access_token(priv_key, user_id, client_id, scope)
|
||||||
-- 生成新 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
|
||||||
|
client.createRefreshToken(user_id, client_id)
|
||||||
-- 生存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)
|
||||||
|
|
@ -193,13 +195,38 @@ end
|
||||||
-- 刷新令牌
|
-- 刷新令牌
|
||||||
local function authorizateRefresh(args)
|
local function authorizateRefresh(args)
|
||||||
-- 1.校验必填参数验证数据是否符合json
|
-- 1.校验必填参数验证数据是否符合json
|
||||||
local ok = validator.validateRefresh(args)
|
local res = validator.validateRefresh(args)
|
||||||
if not ok then
|
if not res then
|
||||||
local result = resp:json(0x000001)
|
local result = resp:json(0x000001)
|
||||||
resp:send(result)
|
resp:send(result)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- 2.
|
-- 2.验证并消费 refresh_token(滚动刷新:生成新的 rt)
|
||||||
|
local rt_data, err = client.consumeRefreshToken(res.refresh_token, res.client_id, true)
|
||||||
|
if not rt_data then
|
||||||
|
ngx.log(ngx.ERR, "refresh_token 验证失败: ", err)
|
||||||
|
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 生成新的 access_token 和 id_token
|
||||||
|
local userinfo = {
|
||||||
|
sub = rt_data.sub,
|
||||||
|
name = "Test User",
|
||||||
|
email = "test@example.com"
|
||||||
|
}
|
||||||
|
-- 3.生成新 Access Token
|
||||||
|
local new_access_token = token.generate_access_token(priv_key, user_id, client_id, scope)
|
||||||
|
-- 生存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.id_token = new_id_token
|
||||||
|
-- 4.返回结果
|
||||||
|
local result = resp:json(ngx.HTTP_OK, ret)
|
||||||
|
resp:send(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 根据授权码获取Access-Token
|
-- 根据授权码获取Access-Token
|
||||||
|
|
@ -238,6 +265,8 @@ function _M:token()
|
||||||
authorizateCode(args)
|
authorizateCode(args)
|
||||||
elseif grant_type == "refresh_token" then
|
elseif grant_type == "refresh_token" then
|
||||||
authorizateRefresh(args)
|
authorizateRefresh(args)
|
||||||
|
else
|
||||||
|
ngx.exit(ngx.HTTP_BAD_REQUEST)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -289,10 +318,10 @@ function _M:userinfo()
|
||||||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||||||
end
|
end
|
||||||
--通过用户id获取用户信息
|
--通过用户id获取用户信息
|
||||||
print("-- get jwt_obj.payload value --")
|
--print("-- get jwt_obj.payload value --")
|
||||||
for key, value in pairs(jwt_obj.payload) do
|
--for key, value in pairs(jwt_obj.payload) do
|
||||||
print("jwt_obj.payload: ", key, " ", value)
|
-- print("jwt_obj.payload: ", key, " ", value)
|
||||||
end
|
--end
|
||||||
local user_id = jwt_obj.payload.sub
|
local user_id = jwt_obj.payload.sub
|
||||||
local code, rest = oauthDao.getUser(user_id)
|
local code, rest = oauthDao.getUser(user_id)
|
||||||
--读取数据错误
|
--读取数据错误
|
||||||
|
|
@ -313,4 +342,24 @@ function _M:userinfo()
|
||||||
resp:send(result)
|
resp:send(result)
|
||||||
end
|
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
|
return _M
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
local red = require("share.redis")
|
local red = require("share.redis")
|
||||||
local cjson = require("cjson.safe")
|
local cjson = require("cjson.safe")
|
||||||
|
local conf = require("config")
|
||||||
|
|
||||||
local _M = {}
|
local _M = {}
|
||||||
|
|
||||||
|
|
@ -41,4 +42,77 @@ 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(关联用户、客户端、过期时间)
|
||||||
|
function _M.createRefreshToken(sub, client_id)
|
||||||
|
local rt = generate_refresh_token()
|
||||||
|
local rt_data = {
|
||||||
|
sub = sub, -- 用户唯一标识
|
||||||
|
client_id = client_id, -- 客户端ID
|
||||||
|
expires_at = ngx.time() + conf.refresh_token_ttl,
|
||||||
|
revoked = false -- 是否吊销
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 存储在 Redis:key=oidc:refresh_token:{rt}
|
||||||
|
local ok, err = red:set("oidc:refresh_token:" .. rt, cjson.encode(rt_data))
|
||||||
|
if not ok then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
red:expire("oidc:refresh_token:"..rt, conf.refresh_token_ttl) -- 设置过期时间
|
||||||
|
return rt
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 验证并消费 refresh_token(支持一次性或滚动刷新)
|
||||||
|
-- 若 rolling=true,则生成新的 refresh_token 并吊销旧的
|
||||||
|
function _M.consumeRefreshToken(rt, client_id, rolling)
|
||||||
|
-- 获取存储的 refresh_token 数据
|
||||||
|
local rt_data_str, err = red:get("oidc:refresh_token:" .. rt)
|
||||||
|
if not rt_data_str or rt_data_str == ngx.null then
|
||||||
|
return nil, "无效的 refresh_token"
|
||||||
|
end
|
||||||
|
|
||||||
|
local rt_data = cjson.decode(rt_data_str)
|
||||||
|
|
||||||
|
-- 验证客户端匹配
|
||||||
|
if rt_data.client_id ~= client_id then
|
||||||
|
return nil, "客户端不匹配"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 验证未过期
|
||||||
|
if rt_data.expires_at < ngx.time() then
|
||||||
|
return nil, "refresh_token 已过期"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 验证未被吊销
|
||||||
|
if rt_data.revoked then
|
||||||
|
return nil, "refresh_token 已吊销"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 滚动刷新:生成新的 refresh_token,吊销旧的
|
||||||
|
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
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
@ -21,13 +21,13 @@ local schema = {
|
||||||
local obj = {
|
local obj = {
|
||||||
header = { typ = "JWT", alg = "HS256" },
|
header = { typ = "JWT", alg = "HS256" },
|
||||||
payload = { -- 自定义数据
|
payload = { -- 自定义数据
|
||||||
userid = "", -- 用户id
|
sub = "", -- 用户id
|
||||||
username = "", -- 用户名
|
username = "", -- 用户名
|
||||||
role_id = "", -- 角色id
|
role_id = "", -- 角色id
|
||||||
role_name = "", -- 角色名称
|
role_name = "", -- 角色名称
|
||||||
--iss = "your_issuer", -- 签发者
|
--iss = "your_issuer", -- 签发者
|
||||||
--sub = "1234567890", -- 主题
|
--sub = "1234567890", -- 主题
|
||||||
exp = ngx.time() + 3600, -- 过期时间(例如:当前时间+1小时)
|
exp = ngx.time() + conf.access_token_ttl, -- 过期时间(例如:当前时间+1小时)
|
||||||
iat = ngx.time() -- 签发时间
|
iat = ngx.time() -- 签发时间
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ function _M.generateToken(userid, username, role_id, role_name)
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
obj.payload.userid = userid
|
obj.payload.sub = userid
|
||||||
obj.payload.username = username
|
obj.payload.username = username
|
||||||
obj.payload.role_id = role_id
|
obj.payload.role_id = role_id
|
||||||
obj.payload.role_name = role_name
|
obj.payload.role_name = role_name
|
||||||
|
|
@ -93,10 +93,6 @@ function _M.authorizationToken(auth_header)
|
||||||
return response
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
local access_token_ttl = 10 * 60 --十分钟
|
|
||||||
local refresh_token_ttl = 7 * 24 * 3600 --7天
|
|
||||||
local id_token_ttl = 60 * 60 --1小时
|
|
||||||
|
|
||||||
-- 生成 Access Token(简化为 JWT 格式)
|
-- 生成 Access Token(简化为 JWT 格式)
|
||||||
function _M.generate_access_token(priv_key, sub, client_id, scope)
|
function _M.generate_access_token(priv_key, sub, client_id, scope)
|
||||||
local now = ngx.time()
|
local now = ngx.time()
|
||||||
|
|
@ -104,7 +100,7 @@ function _M.generate_access_token(priv_key, sub, client_id, scope)
|
||||||
--iss = OP_DOMAIN,
|
--iss = OP_DOMAIN,
|
||||||
sub = sub,
|
sub = sub,
|
||||||
aud = client_id,
|
aud = client_id,
|
||||||
exp = now + access_token_ttl,
|
exp = now + conf.access_token_ttl,
|
||||||
iat = now,
|
iat = now,
|
||||||
scope = scope, --"openid profile email"
|
scope = scope, --"openid profile email"
|
||||||
jti = ngx.md5(now .. math.random() .. client_id) -- 唯一标识
|
jti = ngx.md5(now .. math.random() .. client_id) -- 唯一标识
|
||||||
|
|
@ -123,7 +119,7 @@ function _M.generate_refresh_token(priv_key, sub, client_id, scope)
|
||||||
--iss = OP_DOMAIN,
|
--iss = OP_DOMAIN,
|
||||||
sub = sub,
|
sub = sub,
|
||||||
aud = client_id,
|
aud = client_id,
|
||||||
exp = now + refresh_token_ttl,
|
exp = now + conf.refresh_token_ttl,
|
||||||
iat = now,
|
iat = now,
|
||||||
scope = scope, --"openid profile email"
|
scope = scope, --"openid profile email"
|
||||||
jti = ngx.md5(now .. math.random() * 1000 .. client_id)
|
jti = ngx.md5(now .. math.random() * 1000 .. client_id)
|
||||||
|
|
@ -142,7 +138,7 @@ function _M.generate_id_token(priv_key, sub, client_id, userinfo, scope)
|
||||||
--iss = OP_DOMAIN, -- issuer:OP 域名
|
--iss = OP_DOMAIN, -- issuer:OP 域名
|
||||||
sub = sub, -- subject:用户唯一标识
|
sub = sub, -- subject:用户唯一标识
|
||||||
aud = client_id, -- audience:客户端 ID
|
aud = client_id, -- audience:客户端 ID
|
||||||
exp = now + id_token_ttl, -- 过期时间(1小时)
|
exp = now + conf.id_token_ttl, -- 过期时间(1小时)
|
||||||
iat = now, -- 签发时间
|
iat = now, -- 签发时间
|
||||||
nonce = ngx.var.nonce, -- 可选:防重放攻击
|
nonce = ngx.var.nonce, -- 可选:防重放攻击
|
||||||
--name = userinfo.name,
|
--name = userinfo.name,
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,11 @@ local schemaRefresh = {
|
||||||
type = "object",
|
type = "object",
|
||||||
properties = {
|
properties = {
|
||||||
grant_type = { type = "string" },
|
grant_type = { type = "string" },
|
||||||
|
refresh_token = { type = "string" },
|
||||||
client_id = { type = "string" },
|
client_id = { type = "string" },
|
||||||
client_secret = { type = "string" },
|
client_secret = { type = "string" },
|
||||||
},
|
},
|
||||||
required = { "grant_type", "client_id", "client_secret" }
|
required = { "grant_type", "refresh_token", "client_id", "client_secret" }
|
||||||
}
|
}
|
||||||
|
|
||||||
--根据Refresh-Token刷新Access-Token
|
--根据Refresh-Token刷新Access-Token
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user