智能客服
你问我答,随时在线为你解决问题
华为账号提供了多种通知事件机制,方便元服务感知系统未成年人模式变化和用户信息变更。事件类型包含:
| 事件类型 | 事件分类 | 描述 |
|---|---|---|
| 系统未成年人模式开启/关闭事件 | 客户端事件 | 元服务在前台时通过订阅系统公共事件,实时感知系统未成年人模式的开启/关闭状态。 |
| 用户信息变更事件 | 服务端事件 | 华为账号服务器向元服务服务端发送通知,告知用户及其账户信息的重要变更,如用户取消授权、注销账号、手机号变更等。 |
元服务在前台时可以订阅Account Kit提供的系统未成年人模式公共事件,来感知系统未成年人模式的开启/关闭状态,以提供适龄内容。
以下是系统未成年人模式开启/关闭发送的广播事件。
| 事件名称 | 值 | 描述 |
|---|---|---|
| COMMON_EVENT_MINORSMODE_ON | usual.event.MINORSMODE_ON | 表示系统未成年人模式开启事件。 |
| COMMON_EVENT_MINORSMODE_OFF | usual.event.MINORSMODE_OFF | 表示系统未成年人模式关闭事件。 |
主动开启系统未成年人模式,当前设备会发送系统未成年人模式开启事件。
请先参考“开发准备”的配置签名和指纹章节,通过自动签名方式完成签名信息的配置。请注意,该接口无需配置公钥指纹、Client ID,也无需申请账号权限。
导入commonEventManager模块及相关公共模块。
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
创建订阅者,订阅系统未成年人模式开启或关闭事件。推荐在元服务Ability的onCreate生命周期中调用。
- // 订阅者信息
- const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
- events: [commonEventManager.Support.COMMON_EVENT_MINORSMODE_ON,
- commonEventManager.Support.COMMON_EVENT_MINORSMODE_OFF]
- };
-
- // 定义订阅者,如开发者使用await改写createSubscriber方法,需要把此变量定义到全局(struct外层)
- let subscriber: commonEventManager.CommonEventSubscriber | null = null;
- // 创建订阅者
- commonEventManager.createSubscriber(subscribeInfo)
- .then((commonEventSubscriber: commonEventManager.CommonEventSubscriber) => {
- // 这里获取到commonEventSubscriber对象需要暂存,用于后续事件回调。不可直接使用,否则会出现事件回调不生效的情况
- subscriber = commonEventSubscriber;
- // 订阅公共事件
- commonEventManager.subscribe(subscriber,
- (error: BusinessError, data: commonEventManager.CommonEventData) => {
- if (error) {
- dealCommonEventAllError(error);
- return;
- }
- if (data.event === commonEventManager.Support.COMMON_EVENT_MINORSMODE_ON) {
- // 订阅到开启事件,可以调用获取年龄段的接口,根据年龄段刷新内容展示,同时如开发者有缓存年龄段或系统未成年人模式开启状态,则需要刷新缓存
- return;
- }
- if (data.event === commonEventManager.Support.COMMON_EVENT_MINORSMODE_OFF) {
- // 订阅到关闭事件,关闭当前元服务的未成年人模式,刷新元服务内容展示,取消年龄限制,如开发者有缓存系统未成年人模式开启状态,则需要刷新缓存
- }
- });
- })
- .catch((error: BusinessError) => {
- dealCommonEventAllError(error);
- });
- function dealCommonEventAllError(error: BusinessError): void {
- hilog.error(0x0000, 'testTag', `Failed to subscribe. Code: ${error.code}, message: ${error.message}`);
- }
通过订阅用户信息变更,您可以接收有关用户及其账户的重要更新。当用户及其账户信息发生变更时,华为账号服务器会发送通知到元服务,元服务可以根据通知消息进行自身业务处理。
| 消息名称 | 事件类型 | 事件描述 |
|---|---|---|
| tokens-revoked | https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked | 用户取消元服务的授权 |
| account-purged | https://schemas.openid.net/secevent/risc/event-type/account-purged | 用户注销华为账号 |
| phone-modified | https://schemas.openid.net/secevent/oauth/event-type/phone-modified | 用户授权手机号变更 |
订阅步骤如下:
登录AppGallery Connect,选择“开发与服务”。

在项目列表选择项目。
进入“项目设置 > 开放能力管理”页面,点击“RISC”对应的“管理”。

点击“启用”按钮。

点击“订阅通知”按钮,在弹窗中配置“回调地址”及“订阅范围”,然后点击“提交”。

回调地址:在开启订阅通知后,若华为用户信息发生变更,会发送通知消息到该地址。
订阅范围:订阅的用户信息变更事件,详见事件说明。
华为账号服务器向元服务投递消息。元服务接收到消息后需要先对消息头中的令牌进行验签,确保消息的完整有效性后解析并获取用户信息变更事件详情。具体步骤如下:
验证消息头中的令牌签名。
您可通过任何JWT库(例如:jwt.io)对其进行解析与验证。
无论使用哪种库,您均需完成如下操作:
具体验签逻辑,请参考如下示例代码:
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>
- <dependency>
- <groupId>com.alibaba.fastjson2</groupId>
- <artifactId>fastjson2</artifactId>
- <version>2.0.51</version> <!--此处替换为您项目需要的版本-->
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.5.6</version> <!--此处替换为您项目需要的版本-->
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.26</version> <!--此处替换为您项目需要的版本-->
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.11</version> <!--此处替换为您项目需要的版本-->
- </dependency>
- </dependencies>
Java验签代码示例
- import com.alibaba.fastjson2.JSON;
- import com.alibaba.fastjson2.JSONObject;
- 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.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 lombok.Data;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.http.HttpEntity;
- import org.apache.http.HttpStatus;
- import org.apache.http.client.config.RequestConfig;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.config.Registry;
- import org.apache.http.config.RegistryBuilder;
- import org.apache.http.conn.socket.ConnectionSocketFactory;
- import org.apache.http.conn.socket.PlainConnectionSocketFactory;
- import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.impl.client.HttpClients;
- import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
- import org.apache.http.util.EntityUtils;
- import org.checkerframework.checker.nullness.qual.NonNull;
- import org.checkerframework.checker.nullness.qual.Nullable;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.TrustManagerFactory;
- import java.io.IOException;
- import java.net.URL;
- import java.security.Key;
- import java.security.KeyManagementException;
- import java.security.KeyStore;
- import java.security.KeyStoreException;
- import java.security.NoSuchAlgorithmException;
- import java.security.PublicKey;
- import java.util.Objects;
- import java.util.concurrent.TimeUnit;
-
- /**
- * 订阅和处理用户信息变更
- */
- @Slf4j
- public class RiscDemo {
- /**
- * 公开配置信息地址
- */
- private static final String PUBLIC_CONFIGURATION_URL = "https://risc.cloud.huawei.com/v1beta/public/risc/.well-known/risc-configuration";
-
- /**
- * 公开信息缓存
- */
- private final LoadingCache<String, PublicConfiguration> publicConfigurationCache = Caffeine.newBuilder()
- .expireAfterWrite(1, TimeUnit.DAYS)
- .build(key -> {
- HttpGet request = new HttpGet(PUBLIC_CONFIGURATION_URL);
- try (CloseableHttpResponse response = getClient().execute(request)) {
- HttpEntity responseEntity = response.getEntity();
- String ret = responseEntity != null ? EntityUtils.toString(responseEntity) : null;
- EntityUtils.consume(responseEntity);
- int statusCode = response.getStatusLine().getStatusCode();
- // http状态码不是200,抛出异常
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException("call failed! http status code: " + statusCode + ", response data: " + ret);
- }
- JSONObject configJson = (JSONObject) JSON.parse(ret);
- if (configJson == null) {
- throw new IllegalArgumentException("response param error! http status code: " + statusCode + ", response data: " + ret);
- }
- String issuer = configJson.getString("issuer");
- String jwksUri = configJson.getString("jwks_uri");
- if (Objects.isNull(issuer) || Objects.isNull(jwksUri)) {
- throw new IllegalArgumentException("response param error! http status code: " + statusCode + ", response data: " + ret);
- }
- PublicConfiguration publicConfiguration = new PublicConfiguration();
- publicConfiguration.setIssuer(issuer);
- publicConfiguration.setJwksUri(jwksUri);
- return publicConfiguration;
- }
- });
-
- /**
- * 公钥信息缓存
- */
- private final LoadingCache<String, PublicKey> publicKeyCache = Caffeine.newBuilder()
- .expireAfterWrite(1, TimeUnit.DAYS)
- .build(new CacheLoader<String, PublicKey>() {
- @Override
- public @Nullable PublicKey load(@NonNull String key) throws Exception {
- PublicConfiguration publicConfiguration = getPublicConfiguration();
- JwkProvider huaweiCerts = new UrlJwkProvider(new URL(publicConfiguration.getJwksUri()), null, null);
- return huaweiCerts.get(key).getPublicKey();
- }
- });
-
- /**
- * 调试方法入口
- * @param args main方法入参
- */
- public static void main(String[] args) {
- // 消息请求头中Authorization: Bearer <token>中的<token>
- String token = "<token>";
- // Client ID
- String clientId = "<Client ID>";
- Jwt<?, ?> jwt = new RiscDemo().validateSecurityEventToken(token, clientId);
- if (Objects.isNull(jwt)) {
- // 验签失败
- log.error("verify sign failed");
- } else {
- // 验签成功
- log.info("verify sign success");
- }
- }
-
- /**
- * 对Authorization头域中的token进行验签
- *
- * @param token 消息请求头中Authorization: Bearer <token>中的<token>
- * @param clientId clientId
- * @return 返回为null,则表示验签失败,否则表示验证成功
- */
- public Jwt<?, ?> validateSecurityEventToken(String token, String clientId) {
- try {
- // 公开配置信息中的issuer值
- String issuer = getPublicConfiguration().getIssuer();
- SigningKeyResolver signingKeyResolver = new SigningKeyResolver() {
- private PublicKey getPublicKey(JwsHeader<?> jwsHeader) {
- try {
- return publicKeyCache.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();
- return parser.parse(token);
- } catch (IncorrectClaimException e) {
- // 消息的claim无效,针对异常进行处理(如:日志记录)
- log.error("claim invalid", e);
- } catch (SignatureException e) {
- // 验签失败,针对异常进行处理(如:日志记录)
- log.error("verify signature failed", e);
- } catch (Exception e) {
- // 其他异常,业务自行处理
- log.error("valid event token failed", e);
- }
- return null;
- }
-
- private PublicConfiguration getPublicConfiguration() {
- PublicConfiguration publicConfiguration = this.publicConfigurationCache.get("DEFAULT");
- if (publicConfiguration == null) {
- throw new IllegalArgumentException("public configuration get failed!");
- }
- return publicConfiguration;
- }
-
- private static CloseableHttpClient getClient() {
- PoolingHttpClientConnectionManager connectionManager = buildConnectionManager(
- new String[] {"TLSv1.2"}, new String[] {
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
- });
- connectionManager.setMaxTotal(400);
- connectionManager.setDefaultMaxPerRoute(400);
- RequestConfig config = RequestConfig.custom()
- .setConnectionRequestTimeout(100)
- .setRedirectsEnabled(false)
- .build();
- return HttpClients.custom()
- .useSystemProperties()
- .setConnectionManager(connectionManager)
- .setDefaultRequestConfig(config)
- .build();
- }
-
- private static PoolingHttpClientConnectionManager buildConnectionManager(String[] supportedProtocols,
- String[] supportedCipherSuites) {
- PoolingHttpClientConnectionManager connectionManager = null;
- try {
- SSLContext sc = SSLContext.getInstance("TLSv1.2");
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- tmf.init((KeyStore) null);
- sc.init(null, tmf.getTrustManagers(), null);
- SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sc, supportedProtocols,
- supportedCipherSuites, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
- Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
- .register("http", new PlainConnectionSocketFactory())
- .register("https", sslsf)
- .build();
- connectionManager = new PoolingHttpClientConnectionManager(registry);
- } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
- log.error("build connect manager failed", e);
- }
- return connectionManager;
- }
-
- @Data
- static class PublicConfiguration {
- private String issuer;
- private String jwksUri;
- }
- }
处理消息体。
JSON对象格式消息体
消息示例:用户注销华为账号
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1727619834,
- "jti": "6672ed7d5c5e4c3c92f343ecac40f326",
- "events": {
- "https://schemas.openid.net/secevent/risc/event-type/account-purged": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- }
消息示例:用户取消元服务的授权
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750403661,
- "jti": "97af1abdbbcd4f00a6d8b74c9b1bbb56",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- },
- "scopes": [
- "phone",
- "userConsent",
- "openid",
- "email"
- ]
- }
- }
- }
消息示例:用户授权手机号变更
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750385669,
- "jti": "c27c197ba5c94081aa32b8dbc52389f3",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/phone-modified": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- }
JSON数组格式消息体
消息示例:用户注销华为账号
- [
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750385669,
- "jti": "6672ed7d5c5e4c3c92f343ecac40f326",
- "events": {
- "https://schemas.openid.net/secevent/risc/event-type/account-purged": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- },
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750385669,
- "jti": "6672ed7d5c5e4c3c92f343ecac40f325",
- "events": {
- "https://schemas.openid.net/secevent/risc/event-type/account-purged": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- }
- ]
消息示例:用户取消元服务的授权
- [
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750403661,
- "jti": "97af1abdbbcd4f00a6d8b74c9b1bbb56",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- },
- "scopes": [
- "phone",
- "userConsent",
- "openid",
- "email"
- ]
- }
- }
- },
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750403661,
- "jti": "97af1abdbbcd4f00a6d8b74c9b1bbb57",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- },
- "scopes": [
- "phone",
- "userConsent",
- "openid",
- "email"
- ]
- }
- }
- }
- ]
消息示例:用户授权手机号变更
- [
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750385669,
- "jti": "c27c197ba5c94081aa32b8dbc52389f3",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/phone-modified": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- },
- {
- "iss": "id.cloud.huawei.com",
- "aud": "<Client ID>",
- "iat": 1750385669,
- "jti": "c27c197ba5c94081aa32b8dbc52389f4",
- "events": {
- "https://schemas.openid.net/secevent/oauth/event-type/phone-modified": {
- "subject": {
- "extra": "<触发事件用户的OpenID>",
- "iss": "id.cloud.huawei.com",
- "sub": "<触发事件用户的UnionID>",
- "subject_type": "iss_sub"
- }
- }
- }
- }
- ]
其中,各字段含义如下:
| 参数 | 描述 |
|---|---|
| aud | Client ID(与订阅用户信息变更中提供的Client ID一致)。 |
| iss | 消息投递者标识,固定值“id.cloud.huawei.com”。 |
| iat | 生成该事件的UTC时间戳(秒级)。 |
| jti | 唯一随机字符串,用于标识此消息体,开发者可根据此字段来识别重投消息体。 |
| events | 用户信息变更事件与事件消息体,格式为json。key是用户信息变更事件类型,value为其对应事件消息信息。 |
| subject | 用户信息变更事件对应的消息体,格式为json,包含字段说明如下: - sub:触发事件用户的UnionID(用户在同一个开发者下的所有元服务,此值唯一)。具体格式要求请参考OpenID和UnionID的格式说明。 - subject_type:固定为“iss_sub”。 - extra:触发事件用户的OpenID(用户在同一个元服务中,此值唯一)。具体格式要求请参考OpenID和UnionID的格式说明。 - iss:标识消息投递者,固定为“id.cloud.huawei.com”。 |
| scopes | 取消授权的scope列表,格式为json数组。在事件类型为【https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked】时才存在此字段。 |