onSslErrorEvent:通知用户加载资源(主资源+子资源)时发生SSL错误(证书校验错误),可在该函数中对SSL错误进行处理。
onClientAuthenticationRequest:通知用户收到SSL客户端证书请求事件,可在该函数中触发客户端证书加载,完成双向认证。
在onSslErrorEvent接口中回调通知证书校验失败异常,可通过SslErrorHandler对证书校验失败进行处理,具备能力:
true:表示停止加载页面;
false:表示继续加载页面,与handleConfirm效果类似,默认值为false。
import { webview } from '@kit.ArkWeb';
import { cert } from '@kit.DeviceCertificateKit';
function LogCertInfo(certChainData: Array<Uint8Array> | undefined) {
if (!(certChainData instanceof Array)) {
console.error('failed, cert chain data type is not array');
return;
}
for (let i = 0; i < certChainData.length; i++) {
let encodeBlobData: cert.EncodingBlob = {
data: certChainData[i],
encodingFormat: cert.EncodingFormat.FORMAT_DER
};
cert.createX509Cert(encodeBlobData, (error, x509Cert) => {
if (error) {
console.error('Index : ' + i + ',createX509Cert failed, errCode: ' + error.code + ', errMsg: ' + error.message);
} else {
console.info('createX509Cert success');
console.info(ParseX509CertInfo(x509Cert));
}
});
}
return;
}
function Uint8ArrayToString(dataArray: Uint8Array) {
let dataString = '';
for (let i = 0; i < dataArray.length; i++) {
dataString += String.fromCharCode(dataArray[i]);
}
return dataString;
}
function ParseX509CertInfo(x509Cert: cert.X509Cert) {
let res: string = 'getCertificate success, ' + 'issuer name = ' + Uint8ArrayToString(x509Cert.getIssuerName().data) +
', subject name = ' + Uint8ArrayToString(x509Cert.getSubjectName().data) + ', valid start = ' +
x509Cert.getNotBeforeTime() + ', valid end = ' + x509Cert.getNotAfterTime();
return res;
}
@Entry
@Component
struct Index1 {
controller: webview.WebviewController = new webview.WebviewController();
uiContext: UIContext = this.getUIContext();
build() {
Column() {
// 使用时请替换为真实url
Web({ src: '*****', controller: this.controller })
.onSslErrorEvent((event: SslErrorEvent) => {
console.info('onSslErrorEvent url: ' + event.url);
console.info('onSslErrorEvent error: ' + event.error);
console.info('onSslErrorEvent originalUrl: ' + event.originalUrl);
console.info('onSslErrorEvent referrer: ' + event.referrer);
console.info('onSslErrorEvent isFatalError: ' + event.isFatalError);
console.info('onSslErrorEvent isMainFrame: ' + event.isMainFrame);
LogCertInfo(event.certChainData);
this.uiContext.showAlertDialog({
title: 'onSslErrorEvent',
message: '网页证书校验出错,是否继续加载网页',
primaryButton: {
value: '继续加载',
action: () => {
event.handler.handleConfirm();
}
},
secondaryButton: {
value: '停止加载',
action: () => {
event.handler.handleCancel(true);
}
},
cancel: () => {
event.handler.handleCancel();
}
});
})
.geolocationAccess(false)
.fileAccess(true);
};
}
} 双向认证,客户端发送请求时,需要携带客户端证书,此时依赖客户端设备证书库中已经安装了客户端证书,若是未安装,需要在发送请求前,完成证书安装,再发送请求。实现方式有两种:
// xxx.ets
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';
import { certificateManager } from '@kit.DeviceCertificateKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index2 {
controller: WebviewController = new webview.WebviewController();
uiContext: UIContext = this.getUIContext();
context: Context | undefined = this.uiContext.getHostContext() as common.UIAbilityContext;
uri: string = '';
build() {
Column() {
Button('安装客户端证书至证书库').onClick(() => {
if (!this.context) {
return;
}
// ****指证书名称,使用时请提前替换为真实证书,需提前预置在rawfile目录下
let value: Uint8Array = this.context.resourceManager.getRawFileContentSync('****');
// keystorePwd指证书凭据密码,certAlias指证书凭据别名,使用时请替换成证书真实密码、别名
certificateManager.installPrivateCertificate(value, 'keystorePwd', 'certAlias',
async (err: BusinessError, data: certificateManager.CMResult) => {
console.info(`installPrivateCertificate, uri==========${JSON.stringify(data.uri)}`);
if (!err && data.uri) {
this.uri = data.uri;
}
});
});
Button('加载需要客户端SSL证书的网站')
.onClick(() => {
// 使用时,请替代真实网址
this.controller.loadUrl('****');
});
Web({
// 使用时,请替代真实网址
src: 'www.example.com',
controller: this.controller,
})
.geolocationAccess(false)
.domStorageAccess(true)
.fileAccess(true)
.onPageBegin(event => {
console.info('extensions onpagebegin url ' + event.url);
})
.onClientAuthenticationRequest((event) => {
console.info('onClientAuthenticationRequest ');
event.handler.confirm(this.uri);
return true;
})
.onSslErrorEventReceive(e => {
console.info(`onSslErrorEventReceive->${e.error.toString()}`);
})
.onErrorReceive((event) => {
if (event) {
this.getUIContext().getPromptAction().showToast({
message: `ErrorCode: ${event.error.getErrorCode()}, ErrorInfo: ${event.error.getErrorInfo()}`,
alignment: Alignment.Center
});
console.info('getErrorInfo:' + event.error.getErrorInfo());
console.info('getErrorCode:' + event.error.getErrorCode());
console.info('url:' + event.request.getRequestUrl());
}
})
.onTitleReceive(event => {
console.info('title received ' + event.title);
});
};
}
} 示例代码:
export class GlobalContext {
private constructor() {
}
private static instance: GlobalContext;
private _objects = new Map<string, Object>();
public static getContext(): GlobalContext {
if (!GlobalContext.instance) {
GlobalContext.instance = new GlobalContext();
}
return GlobalContext.instance;
}
getObject(value: string): Object | undefined {
return this._objects.get(value);
}
setObject(key: string, objectClass: Object): void {
this._objects.set(key, objectClass);
}
} import { bundleManager, common, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { GlobalContext } from './GlobalContext';
export default class CertManagerService {
private static sInstance: CertManagerService;
private authUri = '';
private appUid = '';
public static getInstance(): CertManagerService {
if (CertManagerService.sInstance == null) {
CertManagerService.sInstance = new CertManagerService();
}
return CertManagerService.sInstance;
}
async grantAppPm(): Promise<string> {
let bundleFlags =
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
try {
const data = await bundleManager.getBundleInfoForSelf(bundleFlags)
.catch((err: BusinessError) => {
console.error('getBundleInfoForSelf failed. Cause: %{public}s', err.message);
return null;
});
this.appUid = data?.appInfo?.uid?.toString() ?? '';
console.info('getBundleInfoForSelf successfully. Data: %{public}s', JSON.stringify(data));
} catch (err) {
let message = (err as BusinessError).message;
console.error('getBundleInfoForSelf failed: %{public}s', message);
}
// 注:需要在EntryAbility.ets文件的onCreate函数里添加GlobalContext.getContext().setObject("AbilityContext", this.context)
let abilityContext = GlobalContext.getContext().getObject('AbilityContext') as common.UIAbilityContext;
await abilityContext.startAbilityForResult(
{
bundleName: 'com.ohos.certmanager',
abilityName: 'MainAbility',
uri: 'requestAuthorize',
parameters: {
appUid: this.appUid, // 传入申请应用的appUid
}
} as Want)
.then((data: common.AbilityResult) => {
if (!data.resultCode && data.want) {
if (data.want.parameters) {
this.authUri = data.want.parameters.authUri as string; // 授权成功后获取返回的authUri
}
}
});
return this.authUri;
}
}; onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
hilog.info(DOMAIN, 'testTag',
`want ${want ? 'not' : ''} empty, launchParam ${launchParam ? 'not' : ''} empty`);
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
// 将当前Ability的上下文存储到GlobalContext中
GlobalContext.getContext().setObject('AbilityContext', this.context);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
} import { webview } from '@kit.ArkWeb';
import CertManagerService from '../common/CertMgrService';
@Entry
@Component
struct Index {
controller: WebviewController = new webview.WebviewController();
certManager = CertManagerService.getInstance();
aboutToAppear(): void {
webview.WebviewController.setRenderProcessMode(webview.RenderProcessMode.MULTIPLE);
}
build() {
Column() {
Button('加载需要客户端SSL证书的网站')
.onClick(() => {
// 使用时请替换真实网址
this.controller.loadUrl('***');
});
Web({
// 使用时请替换真实网址
src: 'www.example.com',
controller: this.controller,
})
.domStorageAccess(true)
.geolocationAccess(false)
.fileAccess(true)
.onPageBegin(event => {
console.info('extensions onpagebegin url ' + event.url);
})
.onClientAuthenticationRequest((event) => {
console.info('onClientAuthenticationRequest ');
this.certManager.grantAppPm().then(result => {
console.info(`grantAppPm, URI==========${result}`);
event.handler.confirm(result);
});
return true;
})
.onSslErrorEventReceive(e => {
console.info(`onSslErrorEventReceive->${e.error.toString()}`);
})
.onErrorReceive((event) => {
if (event) {
this.getUIContext().getPromptAction().showToast({
message: `ErrorCode: ${event.error.getErrorCode()}, ErrorInfo: ${event.error.getErrorInfo()}`,
alignment: Alignment.Center
});
console.info('getErrorInfo:' + event.error.getErrorInfo());
console.info('getErrorCode:' + event.error.getErrorCode());
console.info('url:' + event.request.getRequestUrl());
}
})
.onTitleReceive(event => {
console.info('title received ' + event.title);
});
};
}
} 证书安装,需要依赖权限:ohos.permission.ACCESS_CERT_MANAGER,
访问在线网页,需要依赖权限:ohos.permission.INTERNET,
申请权限参考权限声明。