鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更

鸿蒙时代
发布于 2025-4-16 17:35
7608浏览
0收藏

一、概述
通过订阅用户信息变更,您可以接收有关用户及其账户的重要更新。当用户取消元服务的授权信息、注销华为账号时,华为账号服务器会发送通知到元服务,元服务可以根据通知消息进行自身业务处理。

二、用户信息变更事件介绍
鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更-鸿蒙开发者社区
三、订阅用户信息变更
订阅步骤如下:

1.登录华为开发者联盟,选择“管理中心 > API服务 > API库”。

2.在App Services找到RISC。

鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更-鸿蒙开发者社区

3.点击启用按钮,选择您的项目,点击确定。

鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更-鸿蒙开发者社区

4.点击订阅通知按钮,在弹窗中配置回调地址及订阅范围。

鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更-鸿蒙开发者社区

说明

回调地址:在开启订阅通知后,若华为用户信息存在变更,会通过发送消息到该地址。

订阅范围:订阅的用户信息变更事件,详见用户信息变更事件介绍。

四、处理通知消息
华为账号服务器向元服务投递消息。元服务接收到消息后需要先对消息头中的令牌进行验签,确保消息的完整有效性后解析并获取用户信息变更事件详情。具体步骤如下:

1.验证消息头中的令牌签名。

您可通过任何JWT库(例如:jwt.io)对其进行解析与验证。

无论使用哪种库,您均需完成如下操作:

调用接口(https://risc.cloud.huawei.com/v1beta/public/risc/.well-known/risc-configuration),获取发行者标识(issuer)与签名密钥证书URI(jwks_uri)。

通过依赖的JWT库,对消息头中的令牌进行解析,获取签名的KeyId。

通过签名的KeyId,从签名密钥证书URI中获取到JWT签名的公钥。

校验JWT签名中的aud与订阅用户信息变更中提供的Client ID一致。

校验JWT签名中的issuer与发行者标识(issuer)一致。

具体验签逻辑,请参考如下示例代码:

Maven依赖配置

<dependencies>
   <dependency>
      <groupId>com.github.ben-manes.caffeine</groupId>
      <artifactId>caffeine</artifactId>
      <version>2.9.3</version> <!--此处替换为您项目需要的版本-->
   </dependency>
   <dependency>   
      <groupId>com.auth0</groupId> 
      <artifactId>jwks-rsa</artifactId>
      <version>0.21.2</version> <!--此处替换为您项目需要的版本-->
   </dependency>
   <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-impl</artifactId>
      <version>0.11.5</version> <!--此处替换为您项目需要的版本-->
   </dependency>
   <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-jackson</artifactId>
      <version>0.11.5</version> <!--此处替换为您项目需要的版本-->
   </dependency>
</dependencies>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

Java验签代码示例

import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.security.SignatureException;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Key;
import java.security.PublicKey;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class RiscDemo {
    public static void main(String[] args) {
        // 消息请求头中Authorization: Bearer <token>中的<token>
        String token = "
<token>
";
        // Client ID
        String clientId = "
<Client ID>
";
        Jwt<?, Object> jwt = validateSecurityEventToken(token, clientId);
        if (Objects.isNull(jwt)) {
            // 验签失败,进行自己逻辑处理
        } else {
            // 验签成功,进行自己逻辑处理
        }
    }

    /**
     * 对Authorization头域中的token进行验签
     *
     * @param token 消息请求头中Authorization: Bearer <token>中的<token>
     * @param clientId Client ID
     *
     * @return 返回为null,则表示验签失败,否则表示验证成功
     */
    public static <H extends Header<H>, B> Jwt<H, B> validateSecurityEventToken(String token, String clientId) {
        Jwt<H, B> jwt = null;
        try {
            /**
             * 公开配置信息地址:https://risc.cloud.huawei.com/v1beta/public/risc/.well-known/risc-configuration
             * 公开配置信息中的issuer值
             */
            String issuer = "id.cloud.huawei.com";
            // 公开配置信息中的jwks_uri值
            String jwksUri = "https://risc.cloud.huawei.com/v1beta/public/risc/certs";
            // 获取公钥信息
            JwkProvider huaweiCerts = new UrlJwkProvider(new URL(jwksUri), null, null);
            LoadingCache<String, PublicKey> cache = Caffeine.newBuilder()
                    .expireAfterWrite(1, TimeUnit.DAYS)
                    .build(new CacheLoader<String, PublicKey>() {
                        @Override
                        public @Nullable PublicKey load(@NonNull String key) throws Exception {
                            return huaweiCerts.get(key).getPublicKey();
                        }
                    });
            SigningKeyResolver signingKeyResolver = new SigningKeyResolver() {
                private PublicKey getPublicKey(JwsHeader<?> jwsHeader) {
                    try {
                        return cache.get(jwsHeader.getKeyId());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                @Override
                public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
                    return getPublicKey(jwsHeader);
                }
                @Override
                public Key resolveSigningKey(JwsHeader jwsHeader, String s) {
                    return getPublicKey(jwsHeader);
                }
            };
            // 验证并解析消息内容
            JwtParser parser = Jwts.parserBuilder()
                    .requireIssuer(issuer)
                    .requireAudience(clientId)
                    .setAllowedClockSkewSeconds(60)
                    .setSigningKeyResolver(signingKeyResolver)
                    .build();
            jwt = parser.parse(token);
        } catch (IncorrectClaimException e) {
            // 消息的claim无效,针对异常进行处理(如:日志记录)
            e.printStackTrace();
        } catch (SignatureException e) {
            // 验签失败,针对异常进行处理(如:日志记录)
            e.printStackTrace();
        } catch (MalformedURLException e) {
            // 无效的jwksUri,检查传入的jwksUri是否与https://risc.cloud.huawei.com/v1beta/public/risc/.well-known/risc-configuration返回jwks_uri一致
            e.printStackTrace();
        } catch (Exception e) {
            // 其他异常,业务自行处理
            e.printStackTrace();
        }
        return jwt;
    }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.

2.处理消息体。

消息体格式如下

{
  "aud": "<
开发者Client ID
>",
  "iss": "id.cloud.huawei.com",
  "iat": 1727619834,
  "jti": "6672ed7d5c5e4c3c92f343ecac40f326",
  "events": {
    "https://schemas.openid.net/secevent/risc/event-type/account-purged": {
      "subject": {
        "sub": "<
触发事件用户的UnionID
>",
        "subject_type": "iss_sub",
        "extra": "<
触发事件用户的OpenID
>",
        "iss": "id.cloud.huawei.com"
      }
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

其中,各字段含义如下:
鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更-鸿蒙开发者社区
本文主要引用参考HarmonyOS官方网站

分类
收藏
回复
举报


回复
    相关推荐