Skip to content
Back to skills

Hmos Devicesecurity Service Account Token

ASecurity

基于服务账号生成JWT格式鉴权令牌,用于Device Security Kit应用设备状态检测服务鉴权,需要华为开发者联盟API Console创建服务账号密钥,仅适用于服务端场景,支持Java语言实现,典型场景为调用应用设备状态检测API前的身份认证

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 22, 2026
developmentgojavaapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned September 22, 2026

npx -y skills add IsKenKenYa/skills --skill hmos-devicesecurity-service-account-token --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hmos Devicesecurity Service Account Token?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hmos Devicesecurity Service Account Token
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/iskenkenya-hmos-devicesecurity-service-account-token/badge)](https://www.skillsdirectory.com/skills/iskenkenya-hmos-devicesecurity-service-account-token)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hmos-devicesecurity-service-account-token
description: 基于服务账号生成JWT格式鉴权令牌,用于Device Security Kit应用设备状态检测服务鉴权,需要华为开发者联盟API Console创建服务账号密钥,仅适用于服务端场景,支持Java语言实现,典型场景为调用应用设备状态检测API前的身份认证
---

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

## 功能描述

本技能用于生成华为服务账号鉴权令牌(JWT格式),实现服务器与服务器之间的接口鉴权。通过华为开发者联盟API Console创建的服务账号密钥文件,使用SHA256withRSA/PSS算法生成符合JWT规范的鉴权令牌,用于调用华为公开API(如Device Security Kit的应用设备状态检测服务)。

**核心能力**:
- 解析服务账号密钥文件(JSON格式)
- 生成JWT Header(包含kid、typ、alg字段)
- 生成JWT Payload(包含aud、iss、exp、iat字段)
- 使用SHA256withRSA/PSS算法生成签名
- 拼接生成完整的JWT Token字符串

**技术特点**:
- 基于标准JWT(JSON Web Token)规范
- 使用PS256(SHA256withRSA/PSS)签名算法
- 令牌有效期建议3600秒
- 需要在服务端环境执行,私钥不能暴露

## 使用场景

### 触发词
- "生成服务账号鉴权令牌"
- "创建JWT Token"
- "Device Security Kit鉴权"
- "服务账号令牌生成"
- "应用设备状态检测服务鉴权"

### 能做
- 根据服务账号密钥文件生成JWT格式鉴权令牌
- 为调用华为公开API提供身份认证
- 支持Java语言的服务端实现
- 自动处理JWT的Header、Payload、Signature三部分生成
- 提供完整的错误处理和参数校验

### 绝不做
- 不在前端或客户端环境执行(私钥必须保密)
- 不生成非JWT格式的令牌
- 不支持除PS256以外的签名算法
- 不提供服务账号密钥文件的创建功能(需在华为开发者联盟创建)
- 不处理令牌的刷新逻辑(需要重新生成)

### 补充
- 服务账号密钥文件必须妥善保管,华为不进行存储
- 应用服务器时间需要校准为标准UTC时间
- JWT令牌有效期建议设置为3600秒(1小时)
- 仅适用于应用设备状态检测服务的鉴权
- 需要申请开发者级的服务账号凭证

## 调用规范和规则

### 输入约束
- **密钥文件格式**:标准JSON格式,必须包含以下字段:
  - `project_id`:项目ID
  - `key_id`:密钥ID(用于JWT Header的kid字段)
  - `private_key`:私钥(PKCS#8格式,BASE64编码)
  - `sub_account`:子账号(用于JWT Payload的iss字段)
  - `auth_uri`、`token_uri`、`auth_provider_cert_uri`、`client_cert_uri`:可选字段
- **私钥长度**:建议2048位或以上
- **时间戳要求**:系统时间必须校准为标准UTC时间
- **有效期设置**:JWT的exp字段建议比iat晚3600秒

### 执行约束
- **执行环境**:必须在服务端环境执行,禁止在客户端执行
- **最大耗时**:单个令牌生成操作应在100ms以内
- **私钥安全**:私钥必须在安全环境存储,禁止明文硬编码
- **算法限制**:仅支持PS256(SHA256withRSA/PSS)签名算法
- **并发限制**:建议实现令牌缓存机制,避免频繁生成

### 内容约束
- **禁止硬编码私钥**:生产环境必须从安全存储读取私钥
- **禁止泄露敏感信息**:生成的JWT令牌、私钥等敏感信息禁止记录到日志
- **禁止使用过期令牌**:令牌过期后必须重新生成
- **禁止使用不安全算法**:仅允许使用PS256,禁止使用RS256等其他算法

### 降级约束
- **私钥读取失败**:返回错误提示,要求检查密钥文件路径和格式
- **时间戳错误**:提示检查系统时间是否校准为标准UTC时间
- **签名生成失败**:检查私钥格式是否为PKCS#8,BASE64解码是否正确
- **网络环境异常**:本技能为本地计算,不依赖网络,但调用华为API时需要网络连接

## 调用流程和步骤

### 步骤1:准备服务账号密钥文件

**前置校验**:
1. 确认已在华为开发者联盟API Console创建服务账号
2. 下载并保存服务账号密钥文件(JSON格式)
3. 确认密钥文件包含必需字段:`key_id`、`private_key`、`sub_account`
4. 确认私钥格式为PKCS#8标准格式

**参数准备**:
```java
// 服务账号密钥文件路径
String keyFilePath = "/path/to/service-account.json";

// 读取密钥文件
JSONObject keyFile = readJsonFile(keyFilePath);
String keyId = keyFile.getString("key_id");
String privateKeyPem = keyFile.getString("private_key");
String iss = keyFile.getString("sub_account");
```

### 步骤2:生成JWT Header

**说明**:JWT Header包含签名算法和密钥ID信息,使用BASE64URL编码。

**示例代码**:
```java
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.StandardCharsets;

// JWT Header数据
JSONObject header = new JSONObject();
header.put("alg", "PS256");  // 固定值:PS256
header.put("typ", "JWT");     // 固定值:JWT
header.put("kid", keyId);     // 来自密钥文件的key_id字段

// BASE64URL编码
byte[] encodeHeaderBytes = Base64.encodeBase64URLSafe(
    header.toString().getBytes(StandardCharsets.UTF_8)
);
String encodeHeader = new String(encodeHeaderBytes, StandardCharsets.UTF_8);
```

**字段说明**:
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| alg | String | 是 | 算法类型,固定值:PS256 |
| typ | String | 是 | 数据类型,固定值:JWT |
| kid | String | 是 | 服务账号密钥文件中的key_id字段 |

### 步骤3:生成JWT Payload

**说明**:JWT Payload包含令牌的声明信息,包括签发者、接收者、有效期等。

**示例代码**:
```java
// JWT Payload数据
long iat = System.currentTimeMillis() / 1000;  // 当前UTC时间戳(秒)
long exp = iat + 3600;  // 过期时间:当前时间 + 3600秒(1小时)

JSONObject payload = new JSONObject();
payload.put("aud", "https://oauth-login.cloud.huawei.com/oauth2/v3/token");  // 固定值
payload.put("iss", iss);  // 来自密钥文件的sub_account字段
payload.put("exp", exp);  // 过期UTC时间戳
payload.put("iat", iat);  // 签发UTC时间戳

// BASE64URL编码
byte[] encodePayloadBytes = Base64.encodeBase64URLSafe(
    payload.toString().getBytes(StandardCharsets.UTF_8)
);
String encodePayload = new String(encodePayloadBytes, StandardCharsets.UTF_8);
```

**字段说明**:
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| aud | String | 是 | 接收者,固定值:https://oauth-login.cloud.huawei.com/oauth2/v3/token |
| iss | String | 是 | 签发者,来自服务账号密钥文件的sub_account字段 |
| exp | Long | 是 | JWT到期UTC时间戳,建议比iat晚3600秒 |
| iat | Long | 是 | JWT签发UTC时间戳,为自UTC时间1970年1月1日00:00:00的秒数 |

### 步骤4:生成JWT Signature

**说明**:使用私钥对Header和Payload的拼接字符串进行签名。

**示例代码**:
```java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;

// 解析私钥
private static PrivateKey getPrivateKey(String key) 
    throws NoSuchAlgorithmException, InvalidKeySpecException {
    // 移除PEM格式的头尾标记
    String privateKeyPEM = key
        .replace("-----BEGIN PRIVATE KEY-----\n", "")
        .replace("\n-----END PRIVATE KEY-----\n", "");
    
    // BASE64解码
    byte[] encoded = Base64.decodeBase64(privateKeyPEM.getBytes(StandardCharsets.UTF_8));
    
    // 生成PrivateKey对象
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePrivate(keySpec);
}

// 生成签名
String jwtHeaderAndPayload = encodeHeader + "." + encodePayload;

Signature signatureInstance = Signature.getInstance(
    "SHA256withRSA/PSS", 
    new BouncyCastleProvider()
);
signatureInstance.initSign(getPrivateKey(privateKeyPem));
signatureInstance.update(jwtHeaderAndPayload.getBytes(StandardCharsets.UTF_8));

String signature = new String(
    Base64.encodeBase64URLSafe(signatureInstance.sign()), 
    StandardCharsets.UTF_8
);
```

**算法说明**:
- 签名算法:SHA256withRSA/PSS(PS256)
- 需要引入BouncyCastle库作为安全提供者
- 私钥格式:PKCS#8标准格式

### 步骤5:拼接生成完整JWT Token

**示例代码**:
```java
// 拼接JWT Token
String jwtToken = encodeHeader + "." + encodePayload + "." + signature;

System.out.println("生成的JWT令牌:" + jwtToken);
return jwtToken;
```

**JWT Token格式**:
```
{Header的BASE64URL编码}.{Payload的BASE64URL编码}.{Signature的BASE64URL编码}
```

**示例**:
```
eyJraWQiOiIxMjM0NTY3ODkwIiwidHlwIjoiSldUIiwiYWxnIjoiUFMyNTYifQ.
eyJhdWQiOiJodHRwczovL29hdXRoLWxvZ2luLmNsb3VkLmh1YXdlaS5jb20vb2F1dGgyL3YzL3Rva2VuIiwiaXNzIjoiMTIzNDU2NzgiLCJleHAiOjE1ODE0MTA2NjQsImlhdCI6MTU4MTQwNzA2NH0.
BRNssabc123def456ghi789jkl012mno345pqr678stu901vwx234yzA567BC890
```

### 步骤6:错误处理

```java
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeySpecException;
import java.security.InvalidKeyException;
import java.security.SignatureException;

public String generateJwtToken(String keyFilePath) {
    try {
        // 步骤1-5的代码
        // ...
        return jwtToken;
        
    } catch (IOException e) {
        System.err.println("密钥文件读取失败:" + e.getMessage());
        throw new RuntimeException("无法读取密钥文件,请检查文件路径和权限", e);
        
    } catch (NoSuchAlgorithmException e) {
        System.err.println("不支持的加密算法:" + e.getMessage());
        throw new RuntimeException("系统不支持RSA算法", e);
        
    } catch (InvalidKeySpecException e) {
        System.err.println("私钥格式错误:" + e.getMessage());
        throw new RuntimeException("私钥格式不正确,请确认为PKCS#8格式", e);
        
    } catch (InvalidKeyException e) {
        System.err.println("私钥无效:" + e.getMessage());
        throw new RuntimeException("私钥无效或已损坏", e);
        
    } catch (SignatureException e) {
        System.err.println("签名生成失败:" + e.getMessage());
        throw new RuntimeException("签名生成失败,请检查私钥和算法配置", e);
        
    } catch (Exception e) {
        System.err.println("未知错误:" + e.getMessage());
        throw new RuntimeException("JWT令牌生成失败", e);
    }
}
```

## 错误码说明

| 错误码 | 说明 | 解决方法 |
|--------|------|----------|
| FILE_NOT_FOUND | 密钥文件不存在或路径错误 | 检查文件路径是否正确,确认文件存在 |
| INVALID_JSON_FORMAT | 密钥文件JSON格式错误 | 验证JSON格式是否正确,检查必需字段是否存在 |
| MISSING_REQUIRED_FIELD | 缺少必需字段(key_id、private_key、sub_account) | 检查密钥文件是否完整,重新从API Console下载 |
| INVALID_PRIVATE_KEY | 私钥格式错误或损坏 | 确认私钥为PKCS#8格式,检查BASE64编码是否正确 |
| ALGORITHM_NOT_SUPPORTED | 系统不支持RSA或PS256算法 | 检查JVM环境,确认已安装BouncyCastle库 |
| TIMESTAMP_ERROR | 系统时间未校准为标准UTC时间 | 同步系统时间,确保时区设置正确 |
| SIGNATURE_FAILED | 签名生成失败 | 检查私钥是否正确,确认算法参数配置 |
| TOKEN_EXPIRED | JWT令牌已过期 | 重新生成令牌,检查exp字段设置 |

## 编译和修复问题

### 依赖声明

**Maven依赖**:
```xml
<dependencies>
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>
    
    <!-- BouncyCastle加密库(PS256算法支持) -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk18on</artifactId>
        <version>1.74</version>
    </dependency>
    
    <!-- BASE64编解码 -->
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
</dependencies>
```

**Gradle依赖**:
```groovy
dependencies {
    implementation 'com.alibaba:fastjson:1.2.83'
    implementation 'org.bouncycastle:bcprov-jdk18on:1.74'
    implementation 'commons-codec:commons-codec:1.15'
}
```

### 环境要求
- **Java版本**:Java 8或更高版本
- **JVM环境**:需要支持RSA加密算法
- **安全提供者**:BouncyCastle库(用于PS256算法)
- **开发环境**:推荐使用IntelliJ IDEA或Eclipse
- **生产环境**:确保私钥文件安全存储,权限设置正确

### 常见编译问题

**问题1:找不到BouncyCastle依赖**
```
java.security.NoSuchAlgorithmException: SHA256withRSA/PSS Signature not available
```
**解决方法**:
- 确认已添加BouncyCastle依赖
- 检查依赖版本是否正确(推荐1.74或更高)
- 确认在代码中注册了BouncyCastleProvider:`new BouncyCastleProvider()`

**问题2:私钥格式错误**
```
java.security.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
```
**解决方法**:
- 确认私钥为PKCS#8格式(PEM格式以`-----BEGIN PRIVATE KEY-----`开头)
- 检查私钥字符串是否包含完整的BASE64编码内容
- 确认已正确移除PEM头尾标记和换行符

**问题3:BASE64解码错误**
```
java.lang.IllegalArgumentException: Illegal base64 character
```
**解决方法**:
- 检查私钥字符串是否包含非法字符
- 确认使用`Base64.decodeBase64()`方法而不是标准的BASE64解码
- 检查私钥字符串是否正确截取(移除头尾标记)

**问题4:时间戳计算错误**
```
JWT令牌验证失败:invalid iat or exp
```
**解决方法**:
- 确认使用`System.currentTimeMillis() / 1000`获取秒级时间戳
- 检查exp是否大于iat(建议exp = iat + 3600)
- 确认系统时间已校准为标准UTC时间

## 常见问题与解决方法

### Q1:如何获取服务账号密钥文件?
**原因**:需要华为开发者联盟账号和项目权限
**解决方法**:
1. 登录华为开发者联盟:https://developer.huawei.com/consumer/cn/console/overview
2. 进入API Console
3. 创建或选择项目
4. 创建服务账号并下载密钥文件
5. 参考:https://developer.huawei.com/consumer/cn/doc/start/api-0000001062522591

### Q2:生成的JWT令牌验证失败怎么办?
**原因**:可能存在多个原因导致验证失败
**解决方法**:
1. 检查Header中的kid是否与密钥文件的key_id一致
2. 检查Payload中的iss是否与密钥文件的sub_account一致
3. 检查系统时间是否校准为标准UTC时间
4. 确认exp字段大于当前时间
5. 确认签名算法为PS256(不是RS256)

### Q3:私钥如何安全存储?
**原因**:私钥泄露会导致严重的安全风险
**解决方法**:
- 使用环境变量存储私钥路径,不硬编码路径
- 使用密钥管理系统(KMS)或加密存储
- 设置文件权限为仅当前用户可读(chmod 600)
- 定期轮换密钥,不长期使用同一密钥
- 生产环境建议使用密钥保险箱服务

### Q4:JWT令牌有效期应该设置多长?
**原因**:有效期过长或过短都会影响使用
**解决方法**:
- 建议设置为3600秒(1小时)
- 不要设置过长(如24小时),存在安全风险
- 不要设置过短(如60秒),可能导致调用时已过期
- 实现令牌缓存机制,在有效期内重复使用
- 令牌过期前5分钟开始刷新

### Q5:如何验证生成的JWT令牌是否正确?
**原因**:需要确认令牌格式和内容正确
**解决方法**:
1. 使用在线JWT解析工具(如https://jwt.io)解析令牌
2. 检查Header部分是否包含kid、typ、alg字段
3. 检查Payload部分是否包含aud、iss、exp、iat字段
4. 检查exp是否为合理的未来时间
5. 调用华为API测试令牌有效性

### Q6:可以在客户端使用这个技能吗?
**原因**:客户端环境安全性无法保证
**解决方法**:
- **绝对禁止**在客户端(如Android、iOS、Web前端)使用此技能
- 私钥必须在服务端环境使用,禁止暴露给客户端
- 客户端应通过服务端接口间接获取鉴权令牌
- 服务端应实现令牌缓存和分发机制

## 输出结果报告

执行完成后输出以下信息:

```json
{
  "status": "success",
  "jwt_token": "eyJraWQiOi...(完整的JWT令牌)",
  "header": {
    "alg": "PS256",
    "typ": "JWT",
    "kid": "*****"
  },
  "payload": {
    "aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
    "iss": "*****",
    "exp": 1581410664,
    "iat": 1581407064
  },
  "token_expiry": "2025-01-15T10:30:00Z",
  "api_used": [
    "SHA256withRSA/PSS签名算法",
    "BASE64URL编码",
    "PKCS#8私钥解析"
  ]
}
```

## 参考文档

- [API开发指南](references/devicesecurity-deviceverify-token.md) - 基于服务账号生成鉴权令牌开发指南
- [JWT规范](https://jwt.io/introduction/) - JSON Web Token介绍
- [华为开发者联盟API Console](https://developer.huawei.com/consumer/cn/console/overview) - 服务账号管理
- [服务账号密钥创建指南](https://developer.huawei.com/consumer/cn/doc/start/api-0000001062522591) - API Console操作指南

## 完整示例代码

- [Java示例代码](assets/JWTGenerateDemo.java) - 完整的JWT令牌生成示例(包含错误处理和参数校验)
- [Maven配置示例](assets/pom.xml) - Maven项目依赖配置
- [服务账号密钥文件示例](assets/service-account-example.json) - 密钥文件格式示例(脱敏)

## 测试用例

### 正向测试用例
- [test_positive_key_validation.java](tests/test_positive_key_validation.java) - 验证正确的密钥文件格式
- [test_positive_token_generation.java](tests/test_positive_token_generation.java) - 验证JWT令牌成功生成
- [test_positive_token_format.java](tests/test_positive_token_format.java) - 验证JWT令牌格式正确性

### 边界测试用例
- [test_boundary_expiry_time.java](tests/test_boundary_expiry_time.java) - 测试令牌有效期边界值(3600秒)
- [test_boundary_private_key_length.java](tests/test_boundary_private_key_length.java) - 测试私钥长度边界
- [test_boundary_timestamp_range.java](tests/test_boundary_timestamp_range.java) - 测试时间戳范围有效性

### 异常测试用例
- [test_exception_invalid_key_format.java](tests/test_exception_invalid_key_format.java) - 测试无效的私钥格式
- [test_exception_missing_fields.java](tests/test_exception_missing_fields.java) - 测试缺少必需字段
- [test_exception_expired_token.java](tests/test_exception_expired_token.java) - 测试过期的令牌验证
- [test_exception_invalid_signature.java](tests/test_exception_invalid_signature.java) - 测试签名验证失败

Files in this skill

  • SUB_SKILL.md18.7 KB
  • assets/JWTGenerateDemo.java11.5 KB
  • assets/pom.xml3.2 KB
  • assets/service-account-example.json678 B
  • assets/service-account-key-example.json738 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…