Просмотр исходного кода

feat(jwt authn): allow to specify JWT field

Ilya Averyanov 3 лет назад
Родитель
Сommit
d0f686d19d

+ 15 - 2
apps/emqx_authn/i18n/emqx_authn_jwt_i18n.conf

@@ -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: """源字段"""
+    }
+  }
+
+
 }

+ 29 - 11
apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl

@@ -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)};

+ 29 - 3
apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl

@@ -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',