feat(jwt authn): allow to specify JWT field

This commit is contained in:
Ilya Averyanov 2022-06-14 12:43:48 +03:00
parent 2ad2da082b
commit d0f686d19d
3 changed files with 73 additions and 16 deletions

View File

@ -217,8 +217,21 @@ Authentication will verify that the value of claims in the JWT (taken from the P
zh: """JWT claim name to use for getting ACL rules.""" zh: """JWT claim name to use for getting ACL rules."""
} }
label { label {
en: """acl_claim_name""" en: """ACL claim name"""
zh: """acl_claim_name""" zh: """ACL claim name"""
} }
} }
from {
desc {
en: """Field to take JWT from."""
zh: """要从中获取 JWT 的字段。"""
}
label {
en: """From Field"""
zh: """源字段"""
}
}
} }

View File

@ -117,7 +117,8 @@ common_fields() ->
default => <<"acl">>, default => <<"acl">>,
desc => ?DESC(acl_claim_name) desc => ?DESC(acl_claim_name)
}}, }},
{verify_claims, fun verify_claims/1} {verify_claims, fun verify_claims/1},
{from, fun from/1}
] ++ emqx_authn_schema:common_fields(). ] ++ emqx_authn_schema:common_fields().
secret(type) -> binary(); secret(type) -> binary();
@ -184,6 +185,11 @@ verify_claims(required) ->
verify_claims(_) -> verify_claims(_) ->
undefined. undefined.
from(type) -> hoconsc:enum([username, password]);
from(desc) -> ?DESC(?FUNCTION_NAME);
from(default) -> password;
from(_) -> undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% APIs %% APIs
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -234,22 +240,25 @@ update(#{use_jwks := true} = Config, _State) ->
authenticate(#{auth_method := _}, _) -> authenticate(#{auth_method := _}, _) ->
ignore; ignore;
authenticate( authenticate(
Credential = #{password := JWT}, Credential,
#{ #{
verify_claims := VerifyClaims0, verify_claims := VerifyClaims0,
jwk := JWK, jwk := JWK,
acl_claim_name := AclClaimName acl_claim_name := AclClaimName,
from := From
} }
) -> ) ->
JWT = maps:get(From, Credential),
JWKs = [JWK], JWKs = [JWK],
VerifyClaims = replace_placeholder(VerifyClaims0, Credential), VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
verify(JWT, JWKs, VerifyClaims, AclClaimName); verify(JWT, JWKs, VerifyClaims, AclClaimName);
authenticate( authenticate(
Credential = #{password := JWT}, Credential,
#{ #{
verify_claims := VerifyClaims0, verify_claims := VerifyClaims0,
jwk_resource := ResourceId, jwk_resource := ResourceId,
acl_claim_name := AclClaimName acl_claim_name := AclClaimName,
from := From
} }
) -> ) ->
case emqx_resource:query(ResourceId, get_jwks) of case emqx_resource:query(ResourceId, get_jwks) of
@ -261,6 +270,7 @@ authenticate(
}), }),
ignore; ignore;
{ok, JWKs} -> {ok, JWKs} ->
JWT = maps:get(From, Credential),
VerifyClaims = replace_placeholder(VerifyClaims0, Credential), VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
verify(JWT, JWKs, VerifyClaims, AclClaimName) verify(JWT, JWKs, VerifyClaims, AclClaimName)
end. end.
@ -281,7 +291,8 @@ create2(#{
secret := Secret0, secret := Secret0,
secret_base64_encoded := Base64Encoded, secret_base64_encoded := Base64Encoded,
verify_claims := VerifyClaims, verify_claims := VerifyClaims,
acl_claim_name := AclClaimName acl_claim_name := AclClaimName,
from := From
}) -> }) ->
case may_decode_secret(Base64Encoded, Secret0) of case may_decode_secret(Base64Encoded, Secret0) of
{error, Reason} -> {error, Reason} ->
@ -291,7 +302,8 @@ create2(#{
{ok, #{ {ok, #{
jwk => JWK, jwk => JWK,
verify_claims => VerifyClaims, verify_claims => VerifyClaims,
acl_claim_name => AclClaimName acl_claim_name => AclClaimName,
from => From
}} }}
end; end;
create2(#{ create2(#{
@ -299,19 +311,22 @@ create2(#{
algorithm := 'public-key', algorithm := 'public-key',
public_key := PublicKey, public_key := PublicKey,
verify_claims := VerifyClaims, verify_claims := VerifyClaims,
acl_claim_name := AclClaimName acl_claim_name := AclClaimName,
from := From
}) -> }) ->
JWK = create_jwk_from_public_key(PublicKey), JWK = create_jwk_from_public_key(PublicKey),
{ok, #{ {ok, #{
jwk => JWK, jwk => JWK,
verify_claims => VerifyClaims, verify_claims => VerifyClaims,
acl_claim_name => AclClaimName acl_claim_name => AclClaimName,
from => From
}}; }};
create2( create2(
#{ #{
use_jwks := true, use_jwks := true,
verify_claims := VerifyClaims, verify_claims := VerifyClaims,
acl_claim_name := AclClaimName acl_claim_name := AclClaimName,
from := From
} = Config } = Config
) -> ) ->
ResourceId = emqx_authn_utils:make_resource_id(?MODULE), ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
@ -324,7 +339,8 @@ create2(
{ok, #{ {ok, #{
jwk_resource => ResourceId, jwk_resource => ResourceId,
verify_claims => VerifyClaims, verify_claims => VerifyClaims,
acl_claim_name => AclClaimName acl_claim_name => AclClaimName,
from => From
}}. }}.
create_jwk_from_public_key(PublicKey) when create_jwk_from_public_key(PublicKey) when
@ -366,6 +382,8 @@ replace_placeholder([{Name, {placeholder, PL}} | More], Variables, Acc) ->
replace_placeholder([{Name, Value} | More], Variables, Acc) -> replace_placeholder([{Name, Value} | More], Variables, Acc) ->
replace_placeholder(More, Variables, [{Name, Value} | Acc]). replace_placeholder(More, Variables, [{Name, Value} | Acc]).
verify(undefined, _, _, _) ->
ignore;
verify(JWT, JWKs, VerifyClaims, AclClaimName) -> verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
case do_verify(JWT, JWKs, VerifyClaims) of case do_verify(JWT, JWKs, VerifyClaims) of
{ok, Extra} -> {ok, acl(Extra, AclClaimName)}; {ok, Extra} -> {ok, acl(Extra, AclClaimName)};

View File

@ -52,10 +52,11 @@ end_per_suite(_) ->
%% Tests %% Tests
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
t_jwt_authenticator_hmac_based(_) -> t_hmac_based(_) ->
Secret = <<"abcdef">>, Secret = <<"abcdef">>,
Config = #{ Config = #{
mechanism => jwt, mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>, acl_claim_name => <<"acl">>,
use_jwks => false, use_jwks => false,
algorithm => 'hmac-based', algorithm => 'hmac-based',
@ -175,11 +176,12 @@ t_jwt_authenticator_hmac_based(_) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State3)), ?assertEqual(ok, emqx_authn_jwt:destroy(State3)),
ok. ok.
t_jwt_authenticator_public_key(_) -> t_public_key(_) ->
PublicKey = test_rsa_key(public), PublicKey = test_rsa_key(public),
PrivateKey = test_rsa_key(private), PrivateKey = test_rsa_key(private),
Config = #{ Config = #{
mechanism => jwt, mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>, acl_claim_name => <<"acl">>,
use_jwks => false, use_jwks => false,
algorithm => 'public-key', algorithm => 'public-key',
@ -202,6 +204,28 @@ t_jwt_authenticator_public_key(_) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State)), ?assertEqual(ok, emqx_authn_jwt:destroy(State)),
ok. ok.
t_jwt_in_username(_) ->
Secret = <<"abcdef">>,
Config = #{
mechanism => jwt,
from => username,
acl_claim_name => <<"acl">>,
use_jwks => false,
algorithm => 'hmac-based',
secret => Secret,
secret_base64_encoded => false,
verify_claims => []
},
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
Payload = #{<<"exp">> => erlang:system_time(second) + 60},
JWS = generate_jws('hmac-based', Payload, Secret),
Credential = #{
username => JWS,
password => <<"pass">>
},
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential, 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),
@ -216,6 +240,7 @@ t_jwks_renewal(_Config) ->
BadConfig0 = #{ BadConfig0 = #{
mechanism => jwt, mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>, acl_claim_name => <<"acl">>,
algorithm => 'public-key', algorithm => 'public-key',
ssl => #{enable => false}, ssl => #{enable => false},
@ -307,10 +332,11 @@ t_jwks_renewal(_Config) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State2)), ?assertEqual(ok, emqx_authn_jwt:destroy(State2)),
ok = emqx_authn_http_test_server:stop(). ok = emqx_authn_http_test_server:stop().
t_jwt_authenticator_verify_claims(_) -> t_verify_claims(_) ->
Secret = <<"abcdef">>, Secret = <<"abcdef">>,
Config0 = #{ Config0 = #{
mechanism => jwt, mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>, acl_claim_name => <<"acl">>,
use_jwks => false, use_jwks => false,
algorithm => 'hmac-based', algorithm => 'hmac-based',