accesstoken-guidelines.md 17.1 KB
Newer Older
Z
zengyawen 已提交
1
# 访问控制授权申请指导
L
l00520400 已提交
2 3 4

## 场景介绍

5
[应用的APL(Ability Privilege Level)等级](accesstoken-overview.md#应用apl等级说明)分为`normal``system_basic``system_core`三个等级,默认情况下,应用的APL等级都为`normal`等级。[权限类型](accesstoken-overview.md#权限类型说明)分为`system_grant``user_grant`两种类型。应用可申请的权限项参见[应用权限列表](permission-list.md)
L
l00520400 已提交
6

7
本文将从如下场景分别介绍:
8

Z
zengyawen 已提交
9 10 11 12 13 14 15 16 17 18 19 20
- [访问控制授权申请指导](#访问控制授权申请指导)
  - [场景介绍](#场景介绍)
  - [配置文件权限声明](#配置文件权限声明)
    - [Stage模型](#stage模型)
    - [FA模型](#fa模型)
    - [权限使用理由的文案内容规范](#权限使用理由的文案内容规范)
  - [ACL方式声明](#acl方式声明)
  - [向用户申请授权](#向用户申请授权)
    - [Stage模型](#stage模型-1)
    - [FA模型](#fa模型-1)
  - [user\_grant权限预授权](#user_grant权限预授权)
  - [相关实例](#相关实例)
21

22
## 配置文件权限声明
Z
zengyawen 已提交
23

24
应用需要在项目的配置文件中逐个声明所需的权限,否则应用将无法获取授权。
25

zyjhandsome's avatar
zyjhandsome 已提交
26 27
> **说明**:
>
28
> 应用在申请`system_basic`和`system_core`等级权限时,需要提升权限等级,因为应用默认的权限等级为`normal`。如果应用需要申请高于默认等级的权限,除了在配置文件中进行声明之外,还需要通过[ACL方式](#acl方式声明)进行声明使用。
29

30
配置文件标签说明如下表所示。
31

32 33 34
| 标签      | 是否必填 | 说明                                                         |
| --------- | -------- | ------------------------------------------------------------ |
| name      | 是       | 权限名称。                                                   |
Z
zengyawen 已提交
35 36
| reason    | 否       | 描述申请权限的原因。<br/> > **说明**:当申请的权限为user_grant权限时,此字段必填。 |
| usedScene | 否       | 描述权限使用的场景和时机,可参考[权限使用理由的文案内容规范](#权限使用理由的文案内容规范)<br/> > **说明**:当申请的权限为user_grant权限时,此字段必填。 |
37 38
| abilities | 否       | 标识需要使用到该权限的Ability,标签为数组形式。<br/>**适用模型**:Stage模型 |
| ability   | 否       | 标识需要使用到该权限的Ability,标签为数组形式。<br/>**适用模型**:FA模型 |
Z
zengyawen 已提交
39
| when      | 否       | 标识权限使用的时机,值为`inuse/always`<br/>- inuse:表示为仅允许前台使用。<br/>- always:表示前后台都可使用。 |
40

41
### Stage模型
42

43
使用Stage模型的应用,需要在[module.json5配置文件](../quick-start/module-configuration-file.md)中声明权限。
44

F
update  
fanchenxuan 已提交
45 46
```json
{
47
  "module" : {
48 49
    // ...
    "requestPermissions":[
50 51 52 53
      {
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
54
          "abilities": [
55 56 57 58 59 60 61 62 63
            "FormAbility"
          ],
          "when":"inuse"
        }
      },
      {
        "name" : "ohos.permission.PERMISSION2",
        "reason": "$string:reason",
        "usedScene": {
64
          "abilities": [
65 66 67 68 69 70 71
            "FormAbility"
          ],
          "when":"always"
        }
      }
    ]
  }
F
update  
fanchenxuan 已提交
72 73 74
}
```

75
### FA模型
F
update  
fanchenxuan 已提交
76

77
使用FA模型的应用,需要在config.json配置文件中声明权限。
F
update  
fanchenxuan 已提交
78

79 80
```json
{
81
  "module" : {
82 83
    // ...
    "reqPermissions":[
84 85 86 87
      {
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
88
          "ability": [
89 90 91
            "FormAbility"
          ],
          "when":"inuse"
92 93 94 95 96 97
        }
      },
      {
        "name" : "ohos.permission.PERMISSION2",
        "reason": "$string:reason",
        "usedScene": {
98
          "ability": [
99 100
            "FormAbility"
          ],
101
          "when":"always"
102 103
        }
      }
104
    ]
105
  }
106 107
}
```
F
update  
fanchenxuan 已提交
108

Z
zengyawen 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
### 权限使用理由的文案内容规范

当申请的权限为user_grant权限时,字段usedScene(权限使用理由和时机)必填。所有的user_grant权限均被归类到某个权限组中,使用时均通过[权限组](accesstoken-overview.md#权限组和子权限)进行申请。当前支持的权限组请查看[应用权限组列表](permission-group-list.md)

**usedScene字段的内容写作规范及建议如下:**

1. 如果需要申请“电话、信息、日历、通讯录、通话记录”这五个权限组的权限,根据工信部要求,需要展示具体子权限的内容与用途。

   **建议句式**

   中文:包括子权限A和子权限B,用于某事。

   英文:Includes: PermissionA and PermissionB. This is used to do/for something.

   > **说明:**<br> 中文使用逗号分割,英文使用句点分割。

   **示例**:用于获取通话状态和移动网络信息,用于安全运营和统计计费服务。

2. 如果当前权限组申请不涉及子权限。

   **建议句式**:用于某事。

   **示例**:用于扫码拍照。

   > **说明:**<br> 保持句子简洁、不要加入多余的分割符号。

3. 用途描述的字串建议小于72个字符(即36个中文字符,UI界面显示大约为两行)。不能超过256个字符,以免在多语言适配后体验不好。

4. 如果不写,将展示默认的申请理由。

**权限使用理由展示方式:**

向用户申请授权时,弹窗将以权限组维度显示,但在应用权限配置时,需要配置每个需要使用的子权限。

该用途有两个展示途径:授权弹窗界面和“设置(Settings)”界面。“设置”的具体路径:设置-隐私-权限管理-某应用某权限详情

系统将使用权限组内当前被申请的第一个子权限的使用理由,作为该权限组的使用理由进行展示。组内的排序,固定按照权限管理内排列的权限组数组顺序。举例说明:权限组A = {权限A, 权限B, 权限C};申请传入的是{权限C, 权限B},界面将展示权限B中的权限使用理由。

zyjhandsome's avatar
zyjhandsome 已提交
147
## ACL方式声明
148

149
当应用需要申请`system_basic``system_core`等级的权限时,比应用默认权限等级`normal`更高。如果需要申请的权限等级高于应用默认的等级,需要使用ACL方式声明使用。
150

151
例如,如果应用需要访问用户公共目录中的音乐文件,需要申请`ohos.permission.WRITE_AUDIO`权限,该权限属于`system_basic`等级。如果应用需要截取屏幕图像,则需要申请`ohos.permission.CAPTURE_SCREEN`权限,该权限属于`system_core`等级。此时,需要将相关权限项配置到[HarmonyAppProvision配置文件](app-provision-structure.md)`acl`字段中。
152

153 154
```json
{
155 156 157
	// ...
	"acls":{
		"allowed-acls":[
158
			"ohos.permission.WRITE_AUDIO",
159
      "ohos.permission.CAPTURE_SCREEN"
160 161
		]
	}
162 163 164
}
```

165
## 向用户申请授权
L
l00520400 已提交
166

167
当应用需要访问用户的隐私信息或使用系统能力时,例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等,应该向用户请求授权。这需要使用 `user_grant` 类型权限。在此之前,应用需要进行权限校验,以判断当前调用者是否具备所需的权限。如果权限校验结果表明当前应用尚未被授权该权限,则应使用动态弹框授权方式,为用户提供手动授权的入口。示意效果如下图所示。
168

Z
zengyawen 已提交
169 170
图1 向用户申请授权

171
![](figures/permission-read_calendar.png)
172

173 174 175
> **说明**:
>
> 每次访问受目标权限保护的接口之前,都需要使用 [requestPermissionsFromUser()](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9) 接口请求相应的权限。用户可能在动态授予权限后通过系统设置来取消应用的权限,因此不能将之前授予的授权状态持久化。
176

177
### Stage模型
178

179 180
以允许应用读取日历信息为例进行说明。

181
1. 申请`ohos.permission.READ_CALENDAR`权限,配置方式请参见[配置文件权限声明](#配置文件权限声明)
182

183 184 185 186 187 188 189 190 191 192 193
2. 校验当前是否已经授权。

   在进行权限申请之前,需要先检查当前应用程序是否已经被授予了权限。可以通过调用[checkAccessToken()](../reference/apis/js-apis-abilityAccessCtrl.md#checkaccesstoken9)方法来校验当前是否已经授权。如果已经授权,则可以直接访问目标操作,否则需要进行下一步操作,即向用户申请授权。

   ```ts
   import bundleManager from '@ohos.bundle.bundleManager';
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
   
   async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
     let atManager = abilityAccessCtrl.createAtManager();
     let grantStatus: abilityAccessCtrl.GrantStatus;
zyjhandsome's avatar
zyjhandsome 已提交
194
   
195 196 197 198 199 200 201
     // 获取应用程序的accessTokenID
     let tokenId: number;
     try {
       let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
       let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
       tokenId = appInfo.accessTokenId;
     } catch (err) {
202
       console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
203 204 205 206 207 208
     }
   
     // 校验应用是否被授予权限
     try {
       grantStatus = await atManager.checkAccessToken(tokenId, permission);
     } catch (err) {
209
       console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
210
     }
zyjhandsome's avatar
zyjhandsome 已提交
211
   
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
     return grantStatus;
   }
   
   async function checkPermissions(): Promise<void> {
     const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
     let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
   
     if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
       // 已经授权,可以继续访问目标操作
     } else {
       // 申请日历权限
     }
   }
   ```

3. 动态向用户申请授权。

   动态向用户申请权限是指在应用程序运行时向用户请求授权的过程。可以通过调用[requestPermissionsFromUser()](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9)方法来实现。该方法接收一个权限列表参数,例如位置、日历、相机、麦克风等。用户可以选择授予权限或者拒绝授权。

   可以在UIAbility的`onWindowStageCreate()`回调中调用[requestPermissionsFromUser()](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9)方法来动态申请权限,也可以根据业务需要在UI中向用户申请授权。

   在UIAbility中向用户申请授权。

235 236
   ```typescript
   import UIAbility from '@ohos.app.ability.UIAbility';
237
   import window from '@ohos.window';
238
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
239
   
240 241
   const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
   
242
   export default class EntryAbility extends UIAbility {
243
     // ...
244
   
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
     onWindowStageCreate(windowStage: window.WindowStage) {
       // Main window is created, set main page for this ability
       let context = this.context;
       let atManager = abilityAccessCtrl.createAtManager();
       // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
   
       atManager.requestPermissionsFromUser(context, permissions).then((data) => {
         let grantStatus: Array<number> = data.authResults;
         let length: number = grantStatus.length;
         for (let i = 0; i < length; i++) {
           if (grantStatus[i] === 0) {
             // 用户授权,可以继续访问目标操作
           } else {
             // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
             return;
           }
         }
         // 授权成功
       }).catch((err) => {
264
         console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
265 266 267
   
       // ...
     }
268 269
   }
   ```
270 271

   在UI中向用户申请授权。
Z
zengyawen 已提交
272

273
   ```typescript
274
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
275 276
   import common from '@ohos.app.ability.common';
   
277 278
   const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
   
279 280 281
   @Entry
   @Component
   struct Index {
282
     reqPermissionsFromUser(permissions: Array<Permissions>): void {
283
       let context = getContext(this) as common.UIAbilityContext;
284
       let atManager = abilityAccessCtrl.createAtManager();
285
       // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
286
       atManager.requestPermissionsFromUser(context, permissions).then((data) => {
287
         let grantStatus: Array<number> = data.authResults;
288 289
         let length: number = grantStatus.length;
         for (let i = 0; i < length; i++) {
290 291 292 293
           if (grantStatus[i] === 0) {
             // 用户授权,可以继续访问目标操作
           } else {
             // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
294 295
             return;
           }
296
         }
297
         // 授权成功
298
       }).catch((err) => {
299
         console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
300 301 302 303 304 305 306 307 308
       })
     }
   
     // 页面展示
     build() {
       // ...
     }
   }
   ```
309

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
4. 处理授权结果。

   调用[requestPermissionsFromUser()](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9)方法后,应用程序将等待用户授权的结果。如果用户授权,则可以继续访问目标操作。如果用户拒绝授权,则需要提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限。

   ```ts
   function openPermissionsInSystemSettings(): void {
     let context = getContext(this) as common.UIAbilityContext;
     let wantInfo = {
       action: 'action.settings.app.info',
       parameters: {
         settingsParamBundleName: 'com.example.myapplication' // 打开指定应用的详情页面
       }
     }
     context.startAbility(wantInfo).then(() => {
       // ...
     }).catch((err) => {
       // ...
     })
   }
   ```

331
### FA模型
L
l00520400 已提交
332

333
通过调用[requestPermissionsFromUser()](../reference/apis/js-apis-inner-app-context.md#contextrequestpermissionsfromuser7)接口向用户动态申请授权。
L
l00520400 已提交
334 335

```js
F
fanchenxuan 已提交
336 337 338 339
import featureAbility from '@ohos.ability.featureAbility';

reqPermissions() {
    let context = featureAbility.getContext();
F
fanchenxuan 已提交
340 341
    let array:Array<string> = ["ohos.permission.PERMISSION2"];
    //requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
F
fanchenxuan 已提交
342
    context.requestPermissionsFromUser(array, 1).then(function(data) {
343 344 345
        console.log("data:" + JSON.stringify(data));
        console.log("data permissions:" + JSON.stringify(data.permissions));
        console.log("data result:" + JSON.stringify(data.authResults));
F
fanchenxuan 已提交
346
    }, (err) => {
347
        console.error('Failed to start ability', err.code);
F
fanchenxuan 已提交
348
    });
349
}
L
l00520400 已提交
350
```
Z
zengyawen 已提交
351

352 353
## user_grant权限预授权
应用在申请`user_grant`类型的权限默认未授权,需要通过拉起弹框由用户确认是否授予该权限。对于一些预制应用,不希望出现弹窗申请`user_grant`类型的权限,例如系统相机应用需要使用麦克风` ohos.permission.MICROPHONE`等权限,需要对麦克风等权限进行预授权,可以通过预授权的方式完成`user_grant`类型权限的授权。[预置配置文件](https://gitee.com/openharmony/vendor_hihope/blob/master/rk3568/preinstall-config/install_list_permissions.json)在设备上的路径为`/system/etc/app/install_list_permission.json`,设备开机启动时会读取该配置文件,在应用安装会对在文件中配置的`user_grant`类型权限授权。预授权配置文件字段内容包括`bundleName``app_signature``permissions`
354

355 356
- `bundleName`字段配置为应用的Bundle名称。
- `app_signature`字段配置为应用的指纹信息。指纹信息的配置参见[应用特权配置指南](../../device-dev/subsystems/subsys-app-privilege-config-guide.md#install_list_capabilityjson中配置)
zyjhandsome's avatar
zyjhandsome 已提交
357
- `permissions`字段中`name`配置为需要预授权的`user_grant`类型的权限名;`permissions`字段中`userCancellable`表示为用户是否能够取消该预授权,配置为true,表示支持用户取消授权,为false则表示不支持用户取消授权。
358

Z
zengyawen 已提交
359
> **说明**:
Z
zengyawen 已提交
360
>
Z
zengyawen 已提交
361
> 当前仅支持预置应用配置该文件。
L
lsq 已提交
362 363 364

```json
[
365
  // ...
L
lsq 已提交
366
  {
367 368
    "bundleName": "com.example.myapplication", // Bundle名称
    "app_signature": ["****"], // 指纹信息
L
lsq 已提交
369 370
    "permissions":[
      {
371 372
        "name": "ohos.permission.PERMISSION_X", // user_grant类型预授权的权限名
        "userCancellable": false // 用户不可取消授权
L
lsq 已提交
373 374
      },
      {
375 376
        "name": "ohos.permission.PERMISSION_Y", // user_grant类型预授权的权限名
        "userCancellable": true // 用户可取消授权
L
lsq 已提交
377 378 379 380 381
      }
    ]
  }
]
```
382

383 384 385 386
## 相关实例

针对访问控制,有以下相关实例可供参考:

Z
zwx1094577 已提交
387
- [AbilityAccessCtrl:访问权限控制(ArkTS)(Full SDK)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Security/AbilityAccessCtrl)