accesstoken-guidelines.md 18.2 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
update  
zengyawen 已提交
9 10 11 12
- [配置文件权限声明](#配置文件权限声明)
- [ACL方式声明](#acl方式声明)
- [向用户申请授权](#向用户申请授权)
- [user_grant权限预授权](#user_grant权限预授权)
13

14
## 配置文件权限声明
Z
zengyawen 已提交
15

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

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

22
配置文件标签说明如下表所示。
23

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

33
### Stage模型
34

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

F
update  
fanchenxuan 已提交
37 38
```json
{
39
  "module" : {
40 41
    // ...
    "requestPermissions":[
42 43 44 45
      {
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
46
          "abilities": [
47 48 49 50 51 52 53 54 55
            "FormAbility"
          ],
          "when":"inuse"
        }
      },
      {
        "name" : "ohos.permission.PERMISSION2",
        "reason": "$string:reason",
        "usedScene": {
56
          "abilities": [
57 58 59 60 61 62 63
            "FormAbility"
          ],
          "when":"always"
        }
      }
    ]
  }
F
update  
fanchenxuan 已提交
64 65 66
}
```

67
### FA模型
F
update  
fanchenxuan 已提交
68

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

71 72
```json
{
73
  "module" : {
74 75
    // ...
    "reqPermissions":[
76 77 78 79
      {
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
80
          "ability": [
81 82 83
            "FormAbility"
          ],
          "when":"inuse"
84 85 86 87 88 89
        }
      },
      {
        "name" : "ohos.permission.PERMISSION2",
        "reason": "$string:reason",
        "usedScene": {
90
          "ability": [
91 92
            "FormAbility"
          ],
93
          "when":"always"
94 95
        }
      }
96
    ]
97
  }
98 99
}
```
F
update  
fanchenxuan 已提交
100

Z
zengyawen 已提交
101 102
### 权限使用理由的文案内容规范

Z
zengyawen 已提交
103
当申请的权限为user_grant权限时,字段reason(申请权限的原因)必填。开发者需要在应用配置文件中,配置每一个需要使用的权限。
Z
zengyawen 已提交
104

Z
zengyawen 已提交
105
但在实际向用户弹窗申请授权时,user_grant权限将会以[权限组](accesstoken-overview.md#权限组和子权限)向用户申请。当前支持的权限组请查看[应用权限组列表](permission-group-list.md)
Z
zengyawen 已提交
106

Z
zengyawen 已提交
107
**reason字段的内容写作规范及建议如下:**
Z
zengyawen 已提交
108

Z
zengyawen 已提交
109
1. 保持句子简洁、不要加入多余的分割符号。
Z
zengyawen 已提交
110 111 112 113 114

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

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

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

Z
zengyawen 已提交
117
3. 如果不写,将展示默认的申请理由。
Z
zengyawen 已提交
118 119 120

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

Z
zengyawen 已提交
121 122 123 124 125 126 127
权限使用理由有两个展示途径:授权弹窗界面和“设置(Settings)”界面。“设置”的具体路径:设置-隐私-权限管理-某应用某权限详情

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

   **句式**:包括子权限A和子权限B,用于某事。

   **样例**:用于获取通话状态和移动网络信息,用于安全运营和统计计费服务。
Z
zengyawen 已提交
128

Z
zengyawen 已提交
129
2. 如果是申请其他权限组中的权限。系统将使用权限组内当前被申请的第一个子权限的使用理由,作为该权限组的使用理由进行展示。组内的排序,固定按照权限管理内排列的权限组数组顺序。
Z
zengyawen 已提交
130

Z
zengyawen 已提交
131
   举例说明:权限组A = {权限A, 权限B, 权限C};申请传入的是{权限C, 权限B},界面将展示权限B中的权限使用理由。
Z
zengyawen 已提交
132

zyjhandsome's avatar
zyjhandsome 已提交
133
## ACL方式声明
134

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

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

139 140
```json
{
141 142 143
	// ...
	"acls":{
		"allowed-acls":[
144
			"ohos.permission.WRITE_AUDIO",
145
      "ohos.permission.CAPTURE_SCREEN"
146 147
		]
	}
148 149 150
}
```

151
## 向用户申请授权
L
l00520400 已提交
152

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

Z
zengyawen 已提交
155 156
图1 向用户申请授权

157
![](figures/permission-read_calendar.png)
158

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

163
### Stage模型
164

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

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

169 170 171 172 173 174 175
2. 校验当前是否已经授权。

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

   ```ts
   import bundleManager from '@ohos.bundle.bundleManager';
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
176
   import { BusinessError } from '@ohos.base';
177 178 179
   
   async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
     let atManager = abilityAccessCtrl.createAtManager();
180
     let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
zyjhandsome's avatar
zyjhandsome 已提交
181
   
182
     // 获取应用程序的accessTokenID
183
     let tokenId: number = 0;
184 185 186 187
     try {
       let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
       let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
       tokenId = appInfo.accessTokenId;
188 189
     } catch (error) {
       let err: BusinessError = error as BusinessError;
190
       console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`);
191 192 193 194 195
     }
   
     // 校验应用是否被授予权限
     try {
       grantStatus = await atManager.checkAccessToken(tokenId, permission);
196 197
     } catch (error) {
       let err: BusinessError = error as BusinessError;
198
       console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`);
199
     }
zyjhandsome's avatar
zyjhandsome 已提交
200
   
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
     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中向用户申请授权。

224 225
   ```typescript
   import UIAbility from '@ohos.app.ability.UIAbility';
226
   import window from '@ohos.window';
227
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
228 229
   import { BusinessError } from '@ohos.base';

230
   const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
231
   export default class EntryAbility extends UIAbility {
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    // ...
    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: BusinessError) => {
        console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
      })
      // ...
    }
256 257
   }
   ```
258 259

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

261
   ```typescript
262
   import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
263
   import common from '@ohos.app.ability.common';
264 265
   import { BusinessError } from '@ohos.base';

266
   const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR'];
267

268 269 270
   @Entry
   @Component
   struct Index {
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    reqPermissionsFromUser(permissions: Array<Permissions>): void {
      let context = getContext(this) as common.UIAbilityContext;
      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: BusinessError) => {
        console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
      })
    }

    // 页面展示
    build() {
      // ...
    }
296 297
   }
   ```
298

299 300 301 302
4. 处理授权结果。

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

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
   ArkTS语法不支持直接使用globalThis,需要通过一个单例的map来做中转。开发者需要:

   a. 在EntryAbility.ets中导入构建的单例对象GlobalThis。
      ```ts
       import { GlobalThis } from '../utils/globalThis'; // 需要根据globalThis.ets的路径自行适配
      ```
   b. 在onCreate中添加:
      ```ts
       GlobalThis.getInstance().setContext('context', this.context);
      ```

   > **说明:**
   >
   > 由于在ts中引入ets文件会有告警提示,需要将EntryAbility.ts的文件后缀修改为EntryAbility.ets,并在module.json5中同步修改。

   **globalThis.ets示例代码如下:**
   ```ts
   import common from '@ohos.app.ability.common';

   // 构造单例对象
   export class GlobalThis {
     private constructor() {}
     private static instance: GlobalThis;
     private _uiContexts = new Map<string, common.UIAbilityContext>();

     public static getInstance(): GlobalThis {
       if (!GlobalThis.instance) {
         GlobalThis.instance = new GlobalThis();
       }
       return GlobalThis.instance;
     }

     getContext(key: string): common.UIAbilityContext | undefined {
       return this._uiContexts.get(key);
     }

     setContext(key: string, value: common.UIAbilityContext): void {
       this._uiContexts.set(key, value);
     }

     // 其他需要传递的内容依此扩展
   }
   ```

347
   ```ts
348 349 350 351 352
   import { BusinessError } from '@ohos.base';
   import Want from '@ohos.app.ability.Want';
   import { GlobalThis } from '../utils/globalThis';
   import common from '@ohos.app.ability.common';

353
   function openPermissionsInSystemSettings(): void {
354 355
     let context: common.UIAbilityContext = GlobalThis.getInstance().getContext('context');
     let wantInfo: Want = {
356 357 358 359 360 361 362
       action: 'action.settings.app.info',
       parameters: {
         settingsParamBundleName: 'com.example.myapplication' // 打开指定应用的详情页面
       }
     }
     context.startAbility(wantInfo).then(() => {
       // ...
363
     }).catch((err: BusinessError) => {
364 365 366 367 368
       // ...
     })
   }
   ```

369
### FA模型
L
l00520400 已提交
370

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

373
```ts
374
import { BusinessError } from '@ohos.base';
F
fanchenxuan 已提交
375 376 377 378
import featureAbility from '@ohos.ability.featureAbility';

reqPermissions() {
    let context = featureAbility.getContext();
F
fanchenxuan 已提交
379 380
    let array:Array<string> = ["ohos.permission.PERMISSION2"];
    //requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
381
    context.requestPermissionsFromUser(array, 1).then(data => {
382 383 384
        console.log("data:" + JSON.stringify(data));
        console.log("data permissions:" + JSON.stringify(data.permissions));
        console.log("data result:" + JSON.stringify(data.authResults));
385
    }, (err: BusinessError) => {
386
        console.error('Failed to start ability', err.code);
F
fanchenxuan 已提交
387
    });
388
}
L
l00520400 已提交
389
```
Z
zengyawen 已提交
390

391
## user_grant权限预授权
392 393

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`
394

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

L
lsq 已提交
399 400
```json
[
401
  // ...
L
lsq 已提交
402
  {
403 404
    "bundleName": "com.example.myapplication", // Bundle名称
    "app_signature": ["****"], // 指纹信息
L
lsq 已提交
405 406
    "permissions":[
      {
407 408
        "name": "ohos.permission.PERMISSION_X", // user_grant类型预授权的权限名
        "userCancellable": false // 用户不可取消授权
L
lsq 已提交
409 410
      },
      {
411 412
        "name": "ohos.permission.PERMISSION_Y", // user_grant类型预授权的权限名
        "userCancellable": true // 用户可取消授权
L
lsq 已提交
413 414 415 416 417
      }
    ]
  }
]
```
418

419 420 421 422
## 相关实例

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

423
- [访问权限控制(ArkTS)(Full SDK)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-4.0-Beta2/code/SystemFeature/Security/AbilityAccessCtrl)
424 425

- [为应用添加运行时权限(ArkTS)(Full SDK)(API9)](https://gitee.com/openharmony/codelabs/tree/master/Security/AccessPermission)