feat(jwt_auth): improve verify_claims handling and docs
This commit is contained in:
parent
712008bc1d
commit
407b0cd0ca
|
@ -39,10 +39,10 @@ jobs:
|
||||||
- name: print erlang log
|
- name: print erlang log
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
cat _build/${{ matrix.profile }}/rel/emqx/logs/erlang.log.*
|
cat _build/${{ matrix.profile }}/rel/emqx/log/erlang.log.*
|
||||||
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: conftest-logs-${{ matrix.profile }}
|
name: conftest-logs-${{ matrix.profile }}
|
||||||
path: _build/${{ matrix.profile }}/rel/emqx/logs
|
path: _build/${{ matrix.profile }}/rel/emqx/log
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
|
@ -62,7 +62,7 @@ stop(Node) when is_atom(Node) ->
|
||||||
unlink(Pid),
|
unlink(Pid),
|
||||||
ok = peer:stop(Pid);
|
ok = peer:stop(Pid);
|
||||||
false ->
|
false ->
|
||||||
ct:pal("The control process for node ~p is unexpetedly down", [Node]),
|
ct:pal("The control process for node ~p is unexpectedly down", [Node]),
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
check_password_from_selected_map/3,
|
check_password_from_selected_map/3,
|
||||||
parse_deep/1,
|
parse_deep/1,
|
||||||
parse_str/1,
|
parse_str/1,
|
||||||
|
parse_str/2,
|
||||||
parse_sql/2,
|
parse_sql/2,
|
||||||
render_deep/2,
|
render_deep/2,
|
||||||
render_str/2,
|
render_str/2,
|
||||||
|
@ -113,28 +114,31 @@ check_password_from_selected_map(Algorithm, Selected, Password) ->
|
||||||
|
|
||||||
parse_deep(Template) ->
|
parse_deep(Template) ->
|
||||||
Result = emqx_template:parse_deep(Template),
|
Result = emqx_template:parse_deep(Template),
|
||||||
handle_disallowed_placeholders(Result, {deep, Template}).
|
handle_disallowed_placeholders(Result, ?ALLOWED_VARS, {deep, Template}).
|
||||||
|
|
||||||
|
parse_str(Template, AllowedVars) ->
|
||||||
|
Result = emqx_template:parse(Template),
|
||||||
|
handle_disallowed_placeholders(Result, AllowedVars, {string, Template}).
|
||||||
|
|
||||||
parse_str(Template) ->
|
parse_str(Template) ->
|
||||||
Result = emqx_template:parse(Template),
|
parse_str(Template, ?ALLOWED_VARS).
|
||||||
handle_disallowed_placeholders(Result, {string, Template}).
|
|
||||||
|
|
||||||
parse_sql(Template, ReplaceWith) ->
|
parse_sql(Template, ReplaceWith) ->
|
||||||
{Statement, Result} = emqx_template_sql:parse_prepstmt(
|
{Statement, Result} = emqx_template_sql:parse_prepstmt(
|
||||||
Template,
|
Template,
|
||||||
#{parameters => ReplaceWith, strip_double_quote => true}
|
#{parameters => ReplaceWith, strip_double_quote => true}
|
||||||
),
|
),
|
||||||
{Statement, handle_disallowed_placeholders(Result, {string, Template})}.
|
{Statement, handle_disallowed_placeholders(Result, ?ALLOWED_VARS, {string, Template})}.
|
||||||
|
|
||||||
handle_disallowed_placeholders(Template, Source) ->
|
handle_disallowed_placeholders(Template, AllowedVars, Source) ->
|
||||||
case emqx_template:validate(?ALLOWED_VARS, Template) of
|
case emqx_template:validate(AllowedVars, Template) of
|
||||||
ok ->
|
ok ->
|
||||||
Template;
|
Template;
|
||||||
{error, Disallowed} ->
|
{error, Disallowed} ->
|
||||||
?tp(warning, "authn_template_invalid", #{
|
?tp(warning, "authn_template_invalid", #{
|
||||||
template => Source,
|
template => Source,
|
||||||
reason => Disallowed,
|
reason => Disallowed,
|
||||||
allowed => #{placeholders => ?ALLOWED_VARS},
|
allowed => #{placeholders => AllowedVars},
|
||||||
notice =>
|
notice =>
|
||||||
"Disallowed placeholders will be rendered as is."
|
"Disallowed placeholders will be rendered as is."
|
||||||
" However, consider using `${$}` escaping for literal `$` where"
|
" However, consider using `${$}` escaping for literal `$` where"
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
-include_lib("emqx_auth/include/emqx_authn.hrl").
|
-include_lib("emqx_auth/include/emqx_authn.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_placeholder.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
create/2,
|
create/2,
|
||||||
|
@ -26,8 +27,9 @@
|
||||||
destroy/1
|
destroy/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-define(ALLOWED_VARS, [
|
||||||
handle_placeholder/1
|
?VAR_CLIENTID,
|
||||||
|
?VAR_USERNAME
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -83,7 +85,7 @@ authenticate(
|
||||||
) ->
|
) ->
|
||||||
JWT = maps:get(From, Credential),
|
JWT = maps:get(From, Credential),
|
||||||
JWKs = [JWK],
|
JWKs = [JWK],
|
||||||
VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
|
VerifyClaims = render_expected(VerifyClaims0, Credential),
|
||||||
verify(JWT, JWKs, VerifyClaims, AclClaimName);
|
verify(JWT, JWKs, VerifyClaims, AclClaimName);
|
||||||
authenticate(
|
authenticate(
|
||||||
Credential,
|
Credential,
|
||||||
|
@ -103,7 +105,7 @@ authenticate(
|
||||||
ignore;
|
ignore;
|
||||||
{ok, JWKs} ->
|
{ok, JWKs} ->
|
||||||
JWT = maps:get(From, Credential),
|
JWT = maps:get(From, Credential),
|
||||||
VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
|
VerifyClaims = render_expected(VerifyClaims0, Credential),
|
||||||
verify(JWT, JWKs, VerifyClaims, AclClaimName)
|
verify(JWT, JWKs, VerifyClaims, AclClaimName)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -203,16 +205,11 @@ may_decode_secret(true, Secret) ->
|
||||||
{error, {invalid_parameter, secret}}
|
{error, {invalid_parameter, secret}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
replace_placeholder(L, Variables) ->
|
render_expected([], _Variables) ->
|
||||||
replace_placeholder(L, Variables, []).
|
[];
|
||||||
|
render_expected([{Name, ExpectedTemplate} | More], Variables) ->
|
||||||
replace_placeholder([], _Variables, Acc) ->
|
Expected = emqx_authn_utils:render_str(ExpectedTemplate, Variables),
|
||||||
Acc;
|
[{Name, Expected} | render_expected(More, Variables)].
|
||||||
replace_placeholder([{Name, {placeholder, PL}} | More], Variables, Acc) ->
|
|
||||||
Value = maps:get(PL, Variables),
|
|
||||||
replace_placeholder(More, Variables, [{Name, Value} | Acc]);
|
|
||||||
replace_placeholder([{Name, Value} | More], Variables, Acc) ->
|
|
||||||
replace_placeholder(More, Variables, [{Name, Value} | Acc]).
|
|
||||||
|
|
||||||
verify(undefined, _, _, _) ->
|
verify(undefined, _, _, _) ->
|
||||||
ignore;
|
ignore;
|
||||||
|
@ -348,23 +345,8 @@ handle_verify_claims(VerifyClaims) ->
|
||||||
handle_verify_claims([], Acc) ->
|
handle_verify_claims([], Acc) ->
|
||||||
Acc;
|
Acc;
|
||||||
handle_verify_claims([{Name, Expected0} | More], Acc) ->
|
handle_verify_claims([{Name, Expected0} | More], Acc) ->
|
||||||
Expected = handle_placeholder(Expected0),
|
Expected1 = emqx_authn_utils:parse_str(Expected0, ?ALLOWED_VARS),
|
||||||
handle_verify_claims(More, [{Name, Expected} | Acc]).
|
handle_verify_claims(More, [{Name, Expected1} | Acc]).
|
||||||
|
|
||||||
handle_placeholder(Placeholder0) ->
|
|
||||||
case re:run(Placeholder0, "^\\$\\{[a-z0-9\\-]+\\}$", [{capture, all}]) of
|
|
||||||
{match, [{Offset, Length}]} ->
|
|
||||||
Placeholder1 = binary:part(Placeholder0, Offset + 2, Length - 3),
|
|
||||||
Placeholder2 = validate_placeholder(Placeholder1),
|
|
||||||
{placeholder, Placeholder2};
|
|
||||||
nomatch ->
|
|
||||||
Placeholder0
|
|
||||||
end.
|
|
||||||
|
|
||||||
validate_placeholder(<<"clientid">>) ->
|
|
||||||
clientid;
|
|
||||||
validate_placeholder(<<"username">>) ->
|
|
||||||
username.
|
|
||||||
|
|
||||||
binary_to_number(Bin) ->
|
binary_to_number(Bin) ->
|
||||||
case string:to_integer(Bin) of
|
case string:to_integer(Bin) of
|
||||||
|
@ -377,7 +359,7 @@ binary_to_number(Bin) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Pars rules which can be in two different formats:
|
%% Parse rules which can be in two different formats:
|
||||||
%% 1. #{<<"pub">> => [<<"a/b">>, <<"c/d">>], <<"sub">> => [...], <<"all">> => [...]}
|
%% 1. #{<<"pub">> => [<<"a/b">>, <<"c/d">>], <<"sub">> => [...], <<"all">> => [...]}
|
||||||
%% 2. [#{<<"permission">> => <<"allow">>, <<"action">> => <<"publish">>, <<"topic">> => <<"a/b">>}, ...]
|
%% 2. [#{<<"permission">> => <<"allow">>, <<"action">> => <<"publish">>, <<"topic">> => <<"a/b">>}, ...]
|
||||||
parse_rules(Rules) when is_map(Rules) ->
|
parse_rules(Rules) when is_map(Rules) ->
|
||||||
|
|
|
@ -28,6 +28,11 @@
|
||||||
|
|
||||||
-include("emqx_auth_jwt.hrl").
|
-include("emqx_auth_jwt.hrl").
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
|
||||||
|
-type validated_value_type() :: clientid | username | binary().
|
||||||
|
|
||||||
|
-reflect_type([validated_value_type/0]).
|
||||||
|
|
||||||
namespace() -> "authn".
|
namespace() -> "authn".
|
||||||
|
|
||||||
|
@ -152,18 +157,29 @@ refresh_interval(validator) -> [fun(I) -> I > 0 end];
|
||||||
refresh_interval(_) -> undefined.
|
refresh_interval(_) -> undefined.
|
||||||
|
|
||||||
verify_claims(type) ->
|
verify_claims(type) ->
|
||||||
%% user input is a map, converted to a list of {binary(), binary()}
|
%% user input is a map, converted to a list of {binary(), validated_value_type()}
|
||||||
typerefl:alias("map", list());
|
typerefl:alias("map", list({binary(), validated_value_type()}));
|
||||||
verify_claims(desc) ->
|
verify_claims(desc) ->
|
||||||
?DESC(?FUNCTION_NAME);
|
?DESC(?FUNCTION_NAME);
|
||||||
verify_claims(default) ->
|
verify_claims(default) ->
|
||||||
[];
|
#{};
|
||||||
verify_claims(validator) ->
|
verify_claims(validator) ->
|
||||||
[fun do_check_verify_claims/1];
|
[fun do_check_verify_claims/1];
|
||||||
verify_claims(converter) ->
|
verify_claims(converter) ->
|
||||||
fun
|
fun
|
||||||
(VerifyClaims) when is_map(VerifyClaims) ->
|
(VerifyClaims) when is_map(VerifyClaims) ->
|
||||||
[{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)];
|
[{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)];
|
||||||
|
(VerifyClaims) when is_list(VerifyClaims) ->
|
||||||
|
lists:map(
|
||||||
|
fun
|
||||||
|
(#{<<"name">> := Key, <<"value">> := Value}) ->
|
||||||
|
{Key, Value};
|
||||||
|
(Other) ->
|
||||||
|
Other
|
||||||
|
end,
|
||||||
|
VerifyClaims
|
||||||
|
);
|
||||||
|
%% this will make validation fail, because it is not a list
|
||||||
(VerifyClaims) ->
|
(VerifyClaims) ->
|
||||||
VerifyClaims
|
VerifyClaims
|
||||||
end;
|
end;
|
||||||
|
@ -174,10 +190,12 @@ verify_claims(_) ->
|
||||||
|
|
||||||
do_check_verify_claims([]) ->
|
do_check_verify_claims([]) ->
|
||||||
true;
|
true;
|
||||||
do_check_verify_claims([{Name, Expected} | More]) ->
|
%% _Expected can't be invalid since tuples may come only from converter
|
||||||
|
do_check_verify_claims([{Name, _Expected} | More]) ->
|
||||||
check_claim_name(Name) andalso
|
check_claim_name(Name) andalso
|
||||||
check_claim_expected(Expected) andalso
|
do_check_verify_claims(More);
|
||||||
do_check_verify_claims(More).
|
do_check_verify_claims(_) ->
|
||||||
|
false.
|
||||||
|
|
||||||
check_claim_name(exp) ->
|
check_claim_name(exp) ->
|
||||||
false;
|
false;
|
||||||
|
@ -193,14 +211,6 @@ check_claim_name(Name) when
|
||||||
check_claim_name(_) ->
|
check_claim_name(_) ->
|
||||||
true.
|
true.
|
||||||
|
|
||||||
check_claim_expected(Expected) ->
|
|
||||||
try emqx_authn_jwt:handle_placeholder(Expected) of
|
|
||||||
_ -> true
|
|
||||||
catch
|
|
||||||
_:_ ->
|
|
||||||
false
|
|
||||||
end.
|
|
||||||
|
|
||||||
from(type) -> hoconsc:enum([username, password]);
|
from(type) -> hoconsc:enum([username, password]);
|
||||||
from(desc) -> ?DESC(?FUNCTION_NAME);
|
from(desc) -> ?DESC(?FUNCTION_NAME);
|
||||||
from(default) -> password;
|
from(default) -> password;
|
||||||
|
|
|
@ -219,6 +219,37 @@ t_jwt_in_username(_) ->
|
||||||
},
|
},
|
||||||
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential, State)).
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential, State)).
|
||||||
|
|
||||||
|
t_complex_template(_) ->
|
||||||
|
Secret = <<"abcdef">>,
|
||||||
|
Config = #{
|
||||||
|
mechanism => jwt,
|
||||||
|
from => password,
|
||||||
|
acl_claim_name => <<"acl">>,
|
||||||
|
use_jwks => false,
|
||||||
|
algorithm => 'hmac-based',
|
||||||
|
secret => Secret,
|
||||||
|
secret_base64_encoded => false,
|
||||||
|
verify_claims => [{<<"id">>, <<"${username}-${clientid}">>}]
|
||||||
|
},
|
||||||
|
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
|
||||||
|
|
||||||
|
Payload0 = #{<<"id">> => <<"myuser-myclient">>, <<"exp">> => erlang:system_time(second) + 60},
|
||||||
|
JWS0 = generate_jws('hmac-based', Payload0, Secret),
|
||||||
|
Credential0 = #{
|
||||||
|
clientid => <<"myclient">>,
|
||||||
|
username => <<"myuser">>,
|
||||||
|
password => JWS0
|
||||||
|
},
|
||||||
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential0, State)),
|
||||||
|
|
||||||
|
Payload1 = #{<<"id">> => <<"-myclient">>, <<"exp">> => erlang:system_time(second) + 60},
|
||||||
|
JWS1 = generate_jws('hmac-based', Payload1, Secret),
|
||||||
|
Credential1 = #{
|
||||||
|
clientid => <<"myclient">>,
|
||||||
|
password => JWS1
|
||||||
|
},
|
||||||
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential1, State)).
|
||||||
|
|
||||||
t_jwks_renewal(_Config) ->
|
t_jwks_renewal(_Config) ->
|
||||||
{ok, _} = emqx_authn_http_test_server:start_link(?JWKS_PORT, ?JWKS_PATH, server_ssl_opts()),
|
{ok, _} = emqx_authn_http_test_server:start_link(?JWKS_PORT, ?JWKS_PATH, server_ssl_opts()),
|
||||||
ok = emqx_authn_http_test_server:set_handler(fun jwks_handler/2),
|
ok = emqx_authn_http_test_server:set_handler(fun jwks_handler/2),
|
||||||
|
@ -415,6 +446,38 @@ t_verify_claims(_) ->
|
||||||
},
|
},
|
||||||
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential5, State1)).
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential5, State1)).
|
||||||
|
|
||||||
|
t_verify_claim_clientid(_) ->
|
||||||
|
Secret = <<"abcdef">>,
|
||||||
|
Config = #{
|
||||||
|
mechanism => jwt,
|
||||||
|
from => password,
|
||||||
|
acl_claim_name => <<"acl">>,
|
||||||
|
use_jwks => false,
|
||||||
|
algorithm => 'hmac-based',
|
||||||
|
secret => Secret,
|
||||||
|
secret_base64_encoded => false,
|
||||||
|
verify_claims => [{<<"cl">>, <<"${clientid}">>}]
|
||||||
|
},
|
||||||
|
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
|
||||||
|
|
||||||
|
Payload0 = #{<<"cl">> => <<"mycl">>, <<"exp">> => erlang:system_time(second) + 60},
|
||||||
|
JWS0 = generate_jws('hmac-based', Payload0, Secret),
|
||||||
|
Credential0 = #{
|
||||||
|
username => <<"myuser">>,
|
||||||
|
clientid => <<"mycl">>,
|
||||||
|
password => JWS0
|
||||||
|
},
|
||||||
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential0, State)),
|
||||||
|
|
||||||
|
Credential1 = #{
|
||||||
|
username => <<"myuser">>,
|
||||||
|
clientid => <<"mycl-invalid">>,
|
||||||
|
password => JWS0
|
||||||
|
},
|
||||||
|
?assertMatch(
|
||||||
|
{error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential1, State)
|
||||||
|
).
|
||||||
|
|
||||||
t_jwt_not_allow_empty_claim_name(_) ->
|
t_jwt_not_allow_empty_claim_name(_) ->
|
||||||
Request = #{
|
Request = #{
|
||||||
<<"use_jwks">> => false,
|
<<"use_jwks">> => false,
|
||||||
|
@ -450,10 +513,89 @@ t_jwt_not_allow_empty_claim_name(_) ->
|
||||||
)
|
)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
t_schema(_Config) ->
|
||||||
|
RawClaims0 = [
|
||||||
|
#{<<"name">> => <<"a">>, <<"value">> => <<"v">>},
|
||||||
|
#{<<"name">> => <<"b">>, <<"value">> => <<"${username}">>},
|
||||||
|
#{<<"name">> => <<"c">>, <<"value">> => <<"${clientid}">>}
|
||||||
|
],
|
||||||
|
?assertMatch(
|
||||||
|
{ok, [
|
||||||
|
{<<"a">>, <<"v">>},
|
||||||
|
{<<"b">>, <<"${username}">>},
|
||||||
|
{<<"c">>, <<"${clientid}">>}
|
||||||
|
]},
|
||||||
|
check_schema(RawClaims0)
|
||||||
|
),
|
||||||
|
|
||||||
|
RawClaims1 = [#{<<"key">> => <<"a">>, <<"value">> => <<"v">>}],
|
||||||
|
?assertMatch(
|
||||||
|
{error, _},
|
||||||
|
check_schema(RawClaims1)
|
||||||
|
),
|
||||||
|
RawClaims2 = #{
|
||||||
|
<<"a">> => <<"v">>,
|
||||||
|
<<"b">> => <<"${username}">>,
|
||||||
|
<<"c">> => <<"${clientid}">>
|
||||||
|
},
|
||||||
|
?assertMatch(
|
||||||
|
{ok, [
|
||||||
|
{<<"a">>, <<"v">>},
|
||||||
|
{<<"b">>, <<"${username}">>},
|
||||||
|
{<<"c">>, <<"${clientid}">>}
|
||||||
|
]},
|
||||||
|
check_schema(RawClaims2)
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{ok, [{<<"x">>, <<"${foo}">>}]},
|
||||||
|
check_schema(#{<<"x">> => <<"${foo}">>})
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{error, _},
|
||||||
|
check_schema([<<"foo">>])
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{error, _},
|
||||||
|
check_schema([#{}])
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{error, _},
|
||||||
|
check_schema([[]])
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{error, _},
|
||||||
|
check_schema(<<"val">>)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
check_schema(RawClaims) ->
|
||||||
|
Config = #{
|
||||||
|
<<"conf">> =>
|
||||||
|
#{
|
||||||
|
<<"use_jwks">> => false,
|
||||||
|
<<"algorithm">> => <<"hmac-based">>,
|
||||||
|
<<"acl_claim_name">> => <<"acl">>,
|
||||||
|
<<"secret">> => <<"secret">>,
|
||||||
|
<<"mechanism">> => <<"jwt">>,
|
||||||
|
<<"verify_claims">> => RawClaims
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UnionMemberSelector =
|
||||||
|
fun
|
||||||
|
(all_union_members) -> emqx_authn_jwt_schema:refs();
|
||||||
|
({value, Value}) -> emqx_authn_jwt_schema:select_union_member(Value)
|
||||||
|
end,
|
||||||
|
Schema = #{roots => [{conf, hoconsc:union(UnionMemberSelector)}]},
|
||||||
|
case emqx_hocon:check(Schema, Config) of
|
||||||
|
{ok, #{conf := #{verify_claims := VerifyClaims}}} ->
|
||||||
|
{ok, VerifyClaims};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
jwks_handler(Req0, State) ->
|
jwks_handler(Req0, State) ->
|
||||||
JWK = jose_jwk:from_pem_file(test_rsa_key(public)),
|
JWK = jose_jwk:from_pem_file(test_rsa_key(public)),
|
||||||
JWKS = jose_jwk_set:to_map([JWK], #{}),
|
JWKS = jose_jwk_set:to_map([JWK], #{}),
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
For JWT authentication, the claims to verify now may be provided as a list of maps:
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name = "claim_name"
|
||||||
|
value = "${username}"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected values now treated as templates, uniformly with the oither authenticators.
|
||||||
|
They now allow arbitrary expressions including `${username}` and `${clientid}` variables.
|
||||||
|
Previousy, only fixed `"${username}"` `"${clientid}"` values were supported for interpolation.
|
||||||
|
|
||||||
|
Improved the documentation for the `verify_claims` parameter.
|
||||||
|
|
|
@ -130,10 +130,17 @@ verify.label:
|
||||||
"""Verify"""
|
"""Verify"""
|
||||||
|
|
||||||
verify_claims.desc:
|
verify_claims.desc:
|
||||||
"""A list of custom claims to validate, which is a list of name/value pairs.
|
"""A list of custom claims to validate. The allowed formats are the following:
|
||||||
|
A map where claim names are map keys and expected values are map values:
|
||||||
|
<code>{ claim_name = "${username}", ...}</code>.
|
||||||
|
|
||||||
|
A list of maps with <code>name</code> (claim name) and <code>value</code> (expected claim value) keys:
|
||||||
|
<code>[{name = "claim_name", value = "${username}"}, ...]</code>.
|
||||||
|
|
||||||
Values can use the following placeholders:
|
Values can use the following placeholders:
|
||||||
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
|
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
|
||||||
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
|
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
|
||||||
|
|
||||||
Authentication will verify that the value of claims in the JWT (taken from the Password field) matches what is required in <code>verify_claims</code>."""
|
Authentication will verify that the value of claims in the JWT (taken from the Password field) matches what is required in <code>verify_claims</code>."""
|
||||||
|
|
||||||
verify_claims.label:
|
verify_claims.label:
|
||||||
|
|
Loading…
Reference in New Issue