文档管理中心

基于服务账号生成鉴权令牌

概述

服务账号(Service Account)是一种可实现服务器与服务器之间接口鉴权的账号,在华为开发者联盟的API Console上创建服务账号,您可根据返回的公私钥在业务应用中生成鉴权令牌,调用支持此类鉴权的华为公开API。

服务账号令牌为JWT(JSON Web Token)格式字符串,JWT数据格式包括三个部分:

  • Header(头部)

  • Payload(负载)

  • Signature(签名)

这三个部分通过“.”进行连接,其中Signature为通过SHA256withRSA/PSS算法对Header与Payload拼接的字符串签名生成的字符串。

示例

收起
自动换行
深色代码主题
复制
  1. eyJra*****JjNjBjMXXX.
  2. eyJhd*****JodHRXXX.
  3. BRNss*****7az5oU7-Zp5g9X2WJVXXX

更多JWT的相关知识请参见Introduction to JSON Web Tokens

开发步骤

  1. 创建服务账号密钥文件。

    您需要在华为开发者联盟的API Console上创建并下载推送服务API的服务账号密钥文件,凭证创建入口如下图所示,选择所在项目,创建“服务账号密钥“凭证。相关创建步骤请参见API服务操作指南-服务账号密钥

    您申请后的服务账号密钥样例文件形式可参考(文件内容已经经过脱敏处理):

    收起
    自动换行
    深色代码主题
    复制
    1. {
    2. "project_id": "*****",
    3. "key_id": "*****",
    4. "private_key": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCKw6kJKtCh7qmMvp1u1dI27z2TKZrPOzHbQaXO/Eez0AWZ2EN+ouF496R3pfo7fQXC1XOT/YTbVC4DNZwWSMA54fu3/AOCY9Zzyi46OK*****==\n-----END PRIVATE KEY-----\n",
    5. "sub_account": "*****",
    6. "auth_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize",
    7. "token_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
    8. "auth_provider_cert_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/certs",
    9. "client_cert_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/x509?client_id="
    10. }
  2. 请确认以上密钥文件中的project_id是否与您的应用所属项目一致。

    您的应用所属项目ID查看方法:登录AppGallery Connect网站,选择“开发与服务”,在项目列表中选择对应的项目,左侧导航栏选择“项目设置”,在该页面获取。

  3. 生成JWT Header数据。

    根据服务账号密钥文件中的key_id(对应示例中的kid)字段拼接以下JSON体,对JSON体进行BASE64编码。

    示例

    收起
    自动换行
    深色代码主题
    复制
    1. {
    2. "kid": "*****",
    3. "typ": "JWT",
    4. "alg": "PS256"
    5. }
    展开
    字段名 描述
    kid 服务账号密钥文件中key_id字段。
    typ 数据类型,固定为:JWT。
    alg 算法类型,固定为:PS256。
  4. 生成JWT Payload数据。

    根据服务账号密钥文件中的sub_account(对应示例中的iss)字段拼接以下JSON体,对JSON体进行BASE64编码。

    示例

    收起
    自动换行
    深色代码主题
    复制
    1. {
    2. "aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
    3. "iss": "*****",
    4. "exp": 1581410664,
    5. "iat": 1581407064
    6. }
    展开
    字段名 描述
    iss 服务账号密钥文件中sub_account字段,标识数据生成者。
    aud 固定为:https://oauth-login.cloud.huawei.com/oauth2/v3/token。
    iat JWT签发UTC时间戳,为自UTC时间1970年1月1日00:00:00起的秒数(您的服务器时间需要校准为标准时间)。
    exp JWT到期UTC时间戳,比iat晚3600秒。
  5. 生成JWT Token。

    将完成BASE64编码后的Header字符串与Payload字符串,通过“.”进行连接,您可在业务应用中,通过服务账号密钥文件中的private_key(华为不进行存储,请您妥善保管),使用SHA256withRSA/PSS算法对拼接的字符串签名。

    至此,您已经完成服务账号鉴权令牌JWT Token的生成。

调用Push Kit REST API

您的应用调用Push Kit REST API时,需要把已获得的服务账号鉴权令牌放在Authorization头部来进行鉴权。请使用v3版本调用Push Kit REST API。

示例

收起
自动换行
深色代码主题
复制
  1. POST "https://push-api.cloud.huawei.com/v3/3158882***52863/messages:send"
  2. Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---****
  3. push-type:0
说明

Authorization格式:Bearer后面拼接空格,再拼接获取的鉴权信息。

接口版本:请使用V3版本调用Push Kit REST API。

场景化消息请求体中,接口URL版本为V3(https://push-api.cloud.huawei.com/v3/[projectId]/messages:send)时,仅支持给HarmonyOS Next/5.x及之后的系统版本推送通知;接口URL版本为V2(https://push-api.cloud.huawei.com/v2/[projectId]/messages:send)时,仅支持给HarmonyOS 3.x/4.x的系统版本推送通知。

示例代码

为了方便您生成服务账号鉴权令牌,我们提供了Java语言的示例代码,请按照说明替换参数运行。

如果您使用其他开发语言,请选择对应的JWT开源组件进行开发。

其中鉴权令牌生成步骤如下:

  1. 完成上述开发步骤中的步骤1创建服务账号密钥文件后,从华为开发者联盟的API Console上创建并下载推送服务API的服务账号密钥文件(.json文件),格式如下:

  2. 以上json文件复制至工程中,参考如下代码进行解析(以private.json为例,本示例基于io.jsonwebtoken:jjwt 0.11.5版本开发,该库各版本API差异较大,请根据实际依赖版本自行适配)。

Java:

收起
自动换行
深色代码主题
复制
  1. /* 推荐的java版本为java8,maven依赖如下:
  2. <dependency>
  3. <groupId>com.fasterxml.jackson.core</groupId>
  4. <artifactId>jackson-databind</artifactId>
  5. <version>2.16.2</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>io.jsonwebtoken</groupId>
  9. <artifactId>jjwt-api</artifactId>
  10. <version>0.11.5</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>io.jsonwebtoken</groupId>
  14. <artifactId>jjwt-impl</artifactId>
  15. <version>0.11.5</version>
  16. <scope>runtime</scope>
  17. </dependency>
  18. <dependency>
  19. <groupId>io.jsonwebtoken</groupId>
  20. <artifactId>jjwt-jackson</artifactId>
  21. <version>0.11.5</version>
  22. <scope>runtime</scope>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.bouncycastle</groupId>
  26. <artifactId>bcprov-jdk18on</artifactId>
  27. <version>1.78.1</version>
  28. <scope>runtime</scope>
  29. </dependency>
  30. */
  31. import com.fasterxml.jackson.databind.JsonNode;
  32. import com.fasterxml.jackson.databind.ObjectMapper;
  33. import io.jsonwebtoken.*;
  34. import io.jsonwebtoken.lang.Maps;
  35. import java.io.File;
  36. import java.io.IOException;
  37. import java.net.URL;
  38. import java.nio.charset.StandardCharsets;
  39. import java.security.KeyFactory;
  40. import java.security.NoSuchAlgorithmException;
  41. import java.security.PrivateKey;
  42. import java.security.interfaces.RSAPrivateKey;
  43. import java.security.spec.InvalidKeySpecException;
  44. import java.security.spec.PKCS8EncodedKeySpec;
  45. import java.util.Base64;
  46. import java.util.Map;
  47. public class JsonWebTokenFactory {
  48. // 实际开发时请将公网地址存储在配置文件或数据库
  49. private static final String AUD = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
  50. public static String createJwt() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, NullPointerException {
  51. // 读取配置文件
  52. ObjectMapper mapper = new ObjectMapper();
  53. // 上述private.json文件放置于工程的src/main/resources路径下
  54. URL url = JsonWebTokenFactory.class.getClassLoader().getResource("private.json");
  55. if (url == null) {
  56. throw new NullPointerException("File not exist");
  57. }
  58. JsonNode rootNode = mapper.readTree(new File(url.getPath()));
  59. RSAPrivateKey privateKey = (RSAPrivateKey) generatePrivateKey(rootNode.get("private_key").asText()
  60. .replace("-----BEGIN PRIVATE KEY-----", "")
  61. .replace("-----END PRIVATE KEY-----", "")
  62. .replaceAll("\\s", ""));
  63. long iat = System.currentTimeMillis() / 1000;
  64. long exp = iat + 3600;
  65. Map<String, Object> header = Maps.<String, Object>of(JwsHeader.KEY_ID, rootNode.get("key_id").asText())
  66. .and(JwsHeader.TYPE, JwsHeader.JWT_TYPE)
  67. .and(JwsHeader.ALGORITHM, SignatureAlgorithm.PS256.getValue())
  68. .build();
  69. Map<String, Object> payload = Maps.<String, Object>of(Claims.ISSUER, rootNode.get("sub_account").asText())
  70. .and(Claims.ISSUED_AT, iat)
  71. .and(Claims.EXPIRATION, exp)
  72. .and(Claims.AUDIENCE, AUD)
  73. .build();
  74. return Jwts.builder()
  75. .setHeader(header)
  76. .setPayload(new ObjectMapper().writeValueAsString(payload))
  77. .signWith(privateKey, SignatureAlgorithm.PS256)
  78. .compact();
  79. }
  80. private static PrivateKey generatePrivateKey(String base64Key) throws NoSuchAlgorithmException, InvalidKeySpecException {
  81. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64Key.getBytes(StandardCharsets.UTF_8)));
  82. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  83. return keyFactory.generatePrivate(keySpec);
  84. }
  85. public static void main(String[] args) {
  86. try {
  87. // 获取鉴权令牌
  88. String jwt = createJwt();
  89. } catch (NoSuchAlgorithmException e) {
  90. // 异常处理流程1
  91. } catch (InvalidKeySpecException e) {
  92. // 异常处理流程2
  93. } catch (IOException e) {
  94. // 异常处理流程3
  95. } catch (NullPointerException e) {
  96. // 异常处理流程4
  97. }
  98. }
  99. }

Node.js:

收起
自动换行
深色代码主题
复制
  1. // 依赖:npm i jsonwebtoken
  2. const jwt = require('jsonwebtoken');
  3. const fs = require('fs');
  4. let privateJson;
  5. try {
  6. // readFileSync首个参数修改为private.json的实际路径
  7. const data = fs.readFileSync('private.json', 'utf8');
  8. privateJson = JSON.parse(data);
  9. // 自定义Header
  10. const header = {
  11. alg: 'PS256', // 建议使用PS256算法
  12. kid: privateJson?.key_id,
  13. typ: 'JWT' // 类型为JWT
  14. };
  15. // 创建JWT载荷
  16. const payload = {
  17. iss: privateJson?.sub_account,
  18. aud: 'https://oauth-login.cloud.huawei.com/oauth2/v3/token', // 实际开发时请将公网地址存储在配置文件或数据库
  19. iat: Math.floor(Date.now() / 1000),
  20. exp: Math.floor(Date.now() / 1000) + 3600
  21. };
  22. const private_key = privateJson?.private_key;
  23. // 将字符串中的 \\n 替换成真正的换行符 \n,再按换行符分割为数组
  24. const lines = private_key.replace(/\\n/g, '\n').split('\n');
  25. // 取前三行
  26. const firstThreeLines = lines.slice(0, 3);
  27. // 重新拼接成一个三行的字符串:
  28. const PRIVATE_KEY = firstThreeLines.join('\n');
  29. // 获取鉴权令牌
  30. const token = jwt.sign(payload, PRIVATE_KEY, { algorithm: 'PS256', header: header });
  31. } catch (error) {
  32. console.error("处理文件时出错:", error);
  33. }

Go:

收起
自动换行
深色代码主题
复制
  1. // 依赖:go get github.com/golang-jwt/jwt/v5
  2. package main
  3. import (
  4. "encoding/json"
  5. "encoding/pem"
  6. "errors"
  7. "fmt"
  8. "github.com/golang-jwt/jwt/v5"
  9. "log"
  10. "os"
  11. "strings"
  12. "time"
  13. )
  14. type ServiceAccountKey struct {
  15. KeyID string `json:"key_id"`
  16. SubAccount string `json:"sub_account"`
  17. PrivateKey string `json:"private_key"`
  18. }
  19. func main() {
  20. // 替换为实际JSON文件路径,此处以本文件同级目录为例
  21. signedToken, err := generateJWTToken("private.json")
  22. if err != nil {
  23. log.Fatalf("Failed to generate JWT token: %v", err)
  24. }
  25. // signedToken为鉴权令牌,调用Push Kit REST API时放在Authorization头部来进行鉴权。
  26. sendMessage(signedToken)
  27. }
  28. func sendMessage(token string) {
  29. // 自行实现业务流程
  30. }
  31. func generateJWTToken(keyFile string) (string, error) {
  32. saKey, err := loadServiceAccountKey(keyFile)
  33. if err != nil {
  34. return "", err
  35. }
  36. formattedPrivateKey, err := formatPrivateKey(saKey.PrivateKey)
  37. if err != nil {
  38. return "", err
  39. }
  40. privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(formattedPrivateKey))
  41. if err != nil {
  42. return "", fmt.Errorf("failed to parse private key: %w", err)
  43. }
  44. token, err := buildJWTToken(saKey.KeyID, saKey.SubAccount)
  45. if err != nil {
  46. return "", err
  47. }
  48. return token.SignedString(privateKey)
  49. }
  50. // buildJWTToken 构造 JWT token 对象
  51. func buildJWTToken(keyID, subAccount string) (*jwt.Token, error) {
  52. now := time.Now().UTC()
  53. iat := now.Unix()
  54. exp := iat + 3600 // token 过期时间:一小时后
  55. claims := jwt.MapClaims{
  56. // 实际开发时请将公网地址存储在配置文件或数据库
  57. "aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
  58. "iss": subAccount,
  59. "exp": exp,
  60. "iat": iat,
  61. }
  62. token := jwt.NewWithClaims(jwt.SigningMethodPS256, claims)
  63. // 设置 header
  64. token.Header["kid"] = keyID
  65. token.Header["typ"] = "JWT"
  66. token.Header["alg"] = "PS256"
  67. return token, nil
  68. }
  69. // loadServiceAccountKey 从 JSON 文件加载服务账号密钥
  70. func loadServiceAccountKey(filename string) (*ServiceAccountKey, error) {
  71. data, err := os.ReadFile(filename)
  72. if err != nil {
  73. return nil, fmt.Errorf("failed to read key file: %w", err)
  74. }
  75. var saKey ServiceAccountKey
  76. if err := json.Unmarshal(data, &saKey); err != nil {
  77. return nil, fmt.Errorf("failed to parse key file: %w", err)
  78. }
  79. if saKey.KeyID == "" || saKey.SubAccount == "" || saKey.PrivateKey == "" {
  80. return nil, errors.New("invalid service account key file: missing required fields")
  81. }
  82. return &saKey, nil
  83. }
  84. // formatPrivateKey 格式化私钥字符串为 PEM 格式
  85. func formatPrivateKey(privateKeyStr string) (string, error) {
  86. trimmed := strings.TrimSpace(privateKeyStr)
  87. // 如果已经是 PEM 格式,则直接返回
  88. if strings.HasPrefix(trimmed, "-----BEGIN PRIVATE KEY-----") &&
  89. strings.HasSuffix(trimmed, "-----END PRIVATE KEY-----") {
  90. return trimmed, nil
  91. }
  92. block, _ := pem.Decode([]byte(trimmed))
  93. if block == nil {
  94. return "", errors.New("failed to decode PEM block")
  95. }
  96. pemBytes := pem.EncodeToMemory(block)
  97. if pemBytes == nil {
  98. return "", errors.New("failed to encode private key to PEM format")
  99. }
  100. return string(pemBytes), nil
  101. }

Python:

收起
自动换行
深色代码主题
复制
  1. # 依赖:pip install PyJWT cryptography
  2. import jwt
  3. import json
  4. import time
  5. from cryptography.hazmat.primitives import serialization
  6. def load_private_key_from_json(json_file_path):
  7. """
  8. 从JSON文件中加载私钥信息
  9. :param json_file_path: JSON文件路径
  10. :return: (key_id, sub_account, private_key_pem)
  11. """
  12. with open(json_file_path, 'r') as f:
  13. data = json.load(f)
  14. # 获取KID和ISS
  15. key_id = data.get('key_id')
  16. sub_account = data.get('sub_account')
  17. # 将私钥转换为PEM格式
  18. private_key_str = data.get('private_key')
  19. private_key_pem = serialization.load_pem_private_key(
  20. private_key_str.encode(),
  21. password=None
  22. )
  23. return key_id, sub_account, private_key_pem
  24. def generate_jwt_token(json_file_path):
  25. # 从JSON文件加载信息
  26. kid, iss, private_key = load_private_key_from_json(json_file_path)
  27. # 当前时间和过期时间(示例中使用固定值,实际应根据需求计算)
  28. iat = int(time.time())
  29. exp = iat + 3600
  30. # 构造Header
  31. header = {
  32. "kid": kid,
  33. "typ": "JWT",
  34. "alg": "PS256"
  35. }
  36. # 构造Payload
  37. payload = {
  38. # 实际开发时请将公网地址存储在配置文件或数据库
  39. "aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
  40. "iss": iss,
  41. "exp": exp,
  42. "iat": iat
  43. }
  44. # 生成JWT Token
  45. token = jwt.encode(
  46. payload=payload,
  47. key=private_key,
  48. algorithm='PS256',
  49. headers=header
  50. )
  51. return token
  52. def send_message(jwt_token):
  53. # 自行实现业务流程
  54. pass
  55. if __name__ == "__main__":
  56. json_file = "private.json" # 替换为实际JSON文件路径,此处以本文件同级目录为例
  57. try:
  58. # jwt_token 为鉴权令牌,调用Push Kit REST API时放在Authorization头部来进行鉴权。
  59. jwt_token = generate_jwt_token(json_file)
  60. send_message(jwt_token)
  61. except Exception as e:
  62. print(f"Error generating JWT token: {str(e)}")

PHP:

收起
自动换行
深色代码主题
复制
  1. <?php
  2. // 依赖:composer require lcobucci/jwt:^5.4.2
  3. // 依赖:composer require lcobucci/jwt-rsassa-pss
  4. // php: ~8.2.0 || ~8.3.0 || ~8.4.0
  5. require 'vendor/autoload.php';
  6. use Lcobucci\JWT\Configuration;
  7. use Lcobucci\JWT\Signer\RsaPss\Sha256;
  8. use Lcobucci\JWT\Signer\Key\InMemory;
  9. class ServiceAccount
  10. {
  11. public string $keyId;
  12. public string $subAccount;
  13. public string $privateKey;
  14. public string $tokenURI;
  15. public function __construct(string $keyId, string $subAccount, string $privateKey, string $tokenURI)
  16. {
  17. $this->keyId = $keyId;
  18. $this->subAccount = $subAccount;
  19. $this->privateKey = $privateKey;
  20. $this->tokenURI = $tokenURI;
  21. }
  22. }
  23. function loadServiceAccount(string $filePath): ServiceAccount
  24. {
  25. if (!file_exists($filePath)) {
  26. throw new RuntimeException("配置文件不存在: $filePath");
  27. }
  28. $json = file_get_contents($filePath);
  29. $config = json_decode($json, true);
  30. if (json_last_error() !== JSON_ERROR_NONE) {
  31. throw new RuntimeException("JSON解析错误: " . json_last_error_msg());
  32. }
  33. // 验证必要字段
  34. $requiredKeys = ['key_id', 'sub_account', 'private_key', 'token_uri'];
  35. foreach ($requiredKeys as $key) {
  36. if (!isset($config[$key])) {
  37. throw new RuntimeException("配置缺少必要字段: $key");
  38. }
  39. }
  40. // 处理私钥中的换行符
  41. $privateKey = str_replace('\n', "\n", $config['private_key']);
  42. return new ServiceAccount(
  43. $config['key_id'],
  44. $config['sub_account'],
  45. $privateKey,
  46. $config['token_uri']
  47. );
  48. }
  49. function sendMessage()
  50. {
  51. // 自行实现业务流程
  52. }
  53. function generateJWTToken(ServiceAccount $serviceAccount)
  54. {
  55. $now = new DateTimeImmutable();
  56. $expire = $now->modify("+3600 seconds");
  57. $configuration = Configuration::forSymmetricSigner(
  58. new Sha256(),
  59. InMemory::plainText($serviceAccount->privateKey)
  60. );
  61. return $configuration->builder()
  62. ->withHeader('alg', 'PS256') // 指定PS256算法
  63. ->withHeader('typ', 'JWT') // JWT类型
  64. ->withHeader('kid', $serviceAccount->keyId) // 密钥ID
  65. ->issuedBy($serviceAccount->subAccount) // iss
  66. ->permittedFor($serviceAccount->tokenURI) // aud
  67. ->issuedAt($now) // iat
  68. ->expiresAt($expire) // exp
  69. ->getToken($configuration->signer(), $configuration->signingKey())
  70. ->toString();
  71. }
  72. function main()
  73. {
  74. try {
  75. // 替换为JSON文件实际路径,此处以与本文件同级目录为例
  76. $filePath = 'private.json';
  77. $serviceAccount = loadServiceAccount($filePath);
  78. $signedToken = generateJWTToken($serviceAccount);
  79. // signedToken为鉴权令牌,调用Push Kit REST API时放在Authorization头部来进行鉴权。
  80. sendMessage($signedToken);
  81. } catch (Exception $e) {
  82. error_log("Error: " . $e->getMessage());
  83. exit(1);
  84. }
  85. }
  86. main();
  87. ?>
在 指南 中进行搜索
请输入您想要搜索的关键词