Files
kong--kong/kong/plugins/basic-auth/access.lua
T
2026-07-13 12:32:21 +08:00

239 lines
6.8 KiB
Lua

local crypto = require "kong.plugins.basic-auth.crypto"
local constants = require "kong.constants"
local crypto_hash = crypto.hash
local decode_base64 = ngx.decode_base64
local re_gmatch = ngx.re.gmatch
local re_match = ngx.re.match
local error = error
local kong = kong
local HEADERS_CONSUMER_ID = constants.HEADERS.CONSUMER_ID
local HEADERS_CONSUMER_CUSTOM_ID = constants.HEADERS.CONSUMER_CUSTOM_ID
local HEADERS_CONSUMER_USERNAME = constants.HEADERS.CONSUMER_USERNAME
local HEADERS_CREDENTIAL_IDENTIFIER = constants.HEADERS.CREDENTIAL_IDENTIFIER
local HEADERS_ANONYMOUS = constants.HEADERS.ANONYMOUS
local _M = {}
-- Fast lookup for credential retrieval depending on the type of the authentication
--
-- All methods must respect:
--
-- @param request ngx request object
-- @param {table} conf Plugin config
-- @return {string} public_key
-- @return {string} private_key
local function retrieve_credentials(header_name, conf, header)
local username, password
local authorization_header = header or kong.request.get_header(header_name)
if authorization_header then
local iterator, iter_err = re_gmatch(authorization_header, "\\s*[Bb]asic\\s*(.+)", "oj")
if not iterator then
kong.log.err(iter_err)
return
end
local m, err = iterator()
if err then
kong.log.err(err)
return
end
if m and m[1] then
local decoded_basic = decode_base64(m[1])
if decoded_basic then
local basic_parts, err = re_match(decoded_basic, "([^:]+):(.*)", "oj")
if err then
kong.log.err(err)
return
end
if not basic_parts then
kong.log.err("header has unrecognized format")
return
end
username = basic_parts[1]
password = basic_parts[2]
end
end
end
if conf.hide_credentials then
kong.service.request.clear_header(header_name)
end
return username, password
end
--- Validate a credential in the Authorization header against one fetched from the database.
-- @param credential The retrieved credential from the username passed in the request
-- @param given_password The password as given in the Authorization header
-- @return Success of authentication
local function validate_credentials(credential, given_password)
local digest, err = crypto_hash(credential.consumer.id, given_password)
if err then
kong.log.err(err)
end
return credential.password == digest
end
local function load_credential_into_memory(username)
local credential, err = kong.db.basicauth_credentials:select_by_username(username)
if err then
return nil, err
end
return credential
end
local function load_credential_from_db(username)
if not username then
return
end
local credential_cache_key = kong.db.basicauth_credentials:cache_key(username)
local credential, err = kong.cache:get(credential_cache_key, nil,
load_credential_into_memory,
username)
if err then
return error(err)
end
return credential
end
local function set_consumer(consumer, credential)
kong.client.authenticate(consumer, credential)
local set_header = kong.service.request.set_header
local clear_header = kong.service.request.clear_header
if consumer and consumer.id then
set_header(HEADERS_CONSUMER_ID, consumer.id)
else
clear_header(HEADERS_CONSUMER_ID)
end
if consumer and consumer.custom_id then
set_header(HEADERS_CONSUMER_CUSTOM_ID, consumer.custom_id)
else
clear_header(HEADERS_CONSUMER_CUSTOM_ID)
end
if consumer and consumer.username then
set_header(HEADERS_CONSUMER_USERNAME, consumer.username)
else
clear_header(HEADERS_CONSUMER_USERNAME)
end
if credential and credential.username then
set_header(HEADERS_CREDENTIAL_IDENTIFIER, credential.username)
else
clear_header(HEADERS_CREDENTIAL_IDENTIFIER)
end
if credential then
clear_header(HEADERS_ANONYMOUS)
else
set_header(HEADERS_ANONYMOUS, true)
end
end
local function unauthorized(message, www_auth_content)
return { status = 401, message = message, headers = { ["WWW-Authenticate"] = www_auth_content } }
end
local function do_authentication(conf)
local www_authenticate = "Basic realm=\"" .. conf.realm .. "\""
local authorization = kong.request.get_header("authorization")
local proxy_authorization = kong.request.get_header("proxy-authorization")
-- If both headers are missing, return 401
if not (authorization or proxy_authorization) then
return false, unauthorized("Unauthorized", www_authenticate)
end
local credential
local given_username, given_password = retrieve_credentials("proxy-authorization", conf, proxy_authorization)
if given_username and given_password then
credential = load_credential_from_db(given_username)
end
-- Try with the authorization header
if not credential then
given_username, given_password = retrieve_credentials("authorization", conf, authorization)
if given_username and given_password then
credential = load_credential_from_db(given_username)
else
return false, unauthorized("Unauthorized", www_authenticate)
end
end
if not credential or not validate_credentials(credential, given_password) then
return false, unauthorized("Unauthorized", www_authenticate)
end
-- Retrieve consumer
local consumer_cache_key = kong.db.consumers:cache_key(credential.consumer.id)
local consumer, err = kong.cache:get(consumer_cache_key, nil,
kong.client.load_consumer,
credential.consumer.id)
if err then
return error(err)
end
set_consumer(consumer, credential)
return true
end
function _M.execute(conf)
if conf.anonymous and kong.client.get_credential() then
-- we're already authenticated, and we're configured for using anonymous,
-- hence we're in a logical OR between auth methods and we're already done.
return
end
local ok, err = do_authentication(conf)
if not ok then
if conf.anonymous then
-- get anonymous user
local consumer_cache_key = kong.db.consumers:cache_key(conf.anonymous)
local consumer, err = kong.cache:get(consumer_cache_key, nil,
kong.client.load_consumer,
conf.anonymous, true)
if err then
return error(err)
end
if not consumer then
local err_msg = "anonymous consumer " .. conf.anonymous .. " is configured but doesn't exist"
kong.log.err(err_msg)
return kong.response.error(500, err_msg)
end
set_consumer(consumer)
else
return kong.response.error(err.status, err.message, err.headers)
end
end
end
return _M