Merge pull request #8210 from savonarola/jwt-authn-add-from

feat(jwt authn): allow to specify JWT field
This commit is contained in:
Ilya Averyanov 2022-06-15 14:56:04 +03:00 committed by GitHub
commit 73dd969843
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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."""
}
label {
en: """acl_claim_name"""
zh: """acl_claim_name"""
en: """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">>,
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().
secret(type) -> binary();
@ -184,6 +185,11 @@ verify_claims(required) ->
verify_claims(_) ->
undefined.
from(type) -> hoconsc:enum([username, password]);
from(desc) -> ?DESC(?FUNCTION_NAME);
from(default) -> password;
from(_) -> undefined.
%%------------------------------------------------------------------------------
%% APIs
%%------------------------------------------------------------------------------
@ -234,22 +240,25 @@ update(#{use_jwks := true} = Config, _State) ->
authenticate(#{auth_method := _}, _) ->
ignore;
authenticate(
Credential = #{password := JWT},
Credential,
#{
verify_claims := VerifyClaims0,
jwk := JWK,
acl_claim_name := AclClaimName
acl_claim_name := AclClaimName,
from := From
}
) ->
JWT = maps:get(From, Credential),
JWKs = [JWK],
VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
verify(JWT, JWKs, VerifyClaims, AclClaimName);
authenticate(
Credential = #{password := JWT},
Credential,
#{
verify_claims := VerifyClaims0,
jwk_resource := ResourceId,
acl_claim_name := AclClaimName
acl_claim_name := AclClaimName,
from := From
}
) ->
case emqx_resource:query(ResourceId, get_jwks) of
@ -261,6 +270,7 @@ authenticate(
}),
ignore;
{ok, JWKs} ->
JWT = maps:get(From, Credential),
VerifyClaims = replace_placeholder(VerifyClaims0, Credential),
verify(JWT, JWKs, VerifyClaims, AclClaimName)
end.
@ -281,7 +291,8 @@ create2(#{
secret := Secret0,
secret_base64_encoded := Base64Encoded,
verify_claims := VerifyClaims,
acl_claim_name := AclClaimName
acl_claim_name := AclClaimName,
from := From
}) ->
case may_decode_secret(Base64Encoded, Secret0) of
{error, Reason} ->
@ -291,7 +302,8 @@ create2(#{
{ok, #{
jwk => JWK,
verify_claims => VerifyClaims,
acl_claim_name => AclClaimName
acl_claim_name => AclClaimName,
from => From
}}
end;
create2(#{
@ -299,19 +311,22 @@ create2(#{
algorithm := 'public-key',
public_key := PublicKey,
verify_claims := VerifyClaims,
acl_claim_name := AclClaimName
acl_claim_name := AclClaimName,
from := From
}) ->
JWK = create_jwk_from_public_key(PublicKey),
{ok, #{
jwk => JWK,
verify_claims => VerifyClaims,
acl_claim_name => AclClaimName
acl_claim_name => AclClaimName,
from => From
}};
create2(
#{
use_jwks := true,
verify_claims := VerifyClaims,
acl_claim_name := AclClaimName
acl_claim_name := AclClaimName,
from := From
} = Config
) ->
ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
@ -324,7 +339,8 @@ create2(
{ok, #{
jwk_resource => ResourceId,
verify_claims => VerifyClaims,
acl_claim_name => AclClaimName
acl_claim_name => AclClaimName,
from => From
}}.
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(More, Variables, [{Name, Value} | Acc]).
verify(undefined, _, _, _) ->
ignore;
verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
case do_verify(JWT, JWKs, VerifyClaims) of
{ok, Extra} -> {ok, acl(Extra, AclClaimName)};

View File

@ -52,10 +52,11 @@ end_per_suite(_) ->
%% Tests
%%------------------------------------------------------------------------------
t_jwt_authenticator_hmac_based(_) ->
t_hmac_based(_) ->
Secret = <<"abcdef">>,
Config = #{
mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>,
use_jwks => false,
algorithm => 'hmac-based',
@ -175,11 +176,12 @@ t_jwt_authenticator_hmac_based(_) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State3)),
ok.
t_jwt_authenticator_public_key(_) ->
t_public_key(_) ->
PublicKey = test_rsa_key(public),
PrivateKey = test_rsa_key(private),
Config = #{
mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>,
use_jwks => false,
algorithm => 'public-key',
@ -202,6 +204,28 @@ t_jwt_authenticator_public_key(_) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State)),
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) ->
{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),
@ -216,6 +240,7 @@ t_jwks_renewal(_Config) ->
BadConfig0 = #{
mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>,
algorithm => 'public-key',
ssl => #{enable => false},
@ -307,10 +332,11 @@ t_jwks_renewal(_Config) ->
?assertEqual(ok, emqx_authn_jwt:destroy(State2)),
ok = emqx_authn_http_test_server:stop().
t_jwt_authenticator_verify_claims(_) ->
t_verify_claims(_) ->
Secret = <<"abcdef">>,
Config0 = #{
mechanism => jwt,
from => password,
acl_claim_name => <<"acl">>,
use_jwks => false,
algorithm => 'hmac-based',