Merge pull request #8210 from savonarola/jwt-authn-add-from
feat(jwt authn): allow to specify JWT field
This commit is contained in:
commit
73dd969843
|
@ -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: """源字段"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)};
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue