目录
1.TLS双向认证
2.TLS双向认证通讯示例
其他鸿蒙开辟文章:
末了
1.TLS双向认证
在上一篇文章鸿蒙开辟实战系列-TLS安全数据传输单向认证示例中,演示了单向认证的方式,也就是客户端必要验证服务端的数字证书,而服务端不必要验证客户端的数字证书,这能满意大部门业务场景,但是,在一些高安全性需求的场景下,服务端也必要确保客户端是可信的,这就要求双向认证,也就是除了客户端验证服务端证书外,服务端也必要验证客户端的数字证书。
要实现双向的认证,就必要服务端在握手时提出客户端的数字证书认证需求,以ECDHE算法的握手过程为例,在第二次握手时,必要服务端发送Certificate Request消息给客户端,表明是双向认证的,在第三次握手时,客户端发送Certificate消息给服务端,其中就包罗证书信息。
2.TLS双向认证通讯示例
本文将实现一个双向认证的示例,应用运行后的界面如图所示:

在这个应用里,用户可以选择服务端的CA文件,作为验证服务端证书有效性的依据;还必要选择客户端证书文件和客户端私钥文件,用来执行服务端对客户端的认证。这三个文件都选择并加载后,就可以单击“连接”按钮连接服务端了,连接成功后可以发送消息给服务端。
下面详细介绍创建该应用的步调。
步调1:创建Empty Ability项目。
步调2:在module.json5配置文件加上对权限的声明:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_WIFI_INFO"
}
]
这里分别添加了访问互联网和访问WIFI信息的权限。
步调3:在Index.ets文件里添加如下的代码:
- import socket from '@ohos.net.socket';
- import wifiManager from '@ohos.wifiManager';
- import systemDateTime from '@ohos.systemDateTime';
- import util from '@ohos.util';
- import picker from '@ohos.file.picker';
- import fs from '@ohos.file.fs';
- import common from '@ohos.app.ability.common';
- //执行TLS通讯的对象
- let tlsSocket = socket.constructTLSSocketInstance()
- //说明:本地的IP地址不是必须知道的,绑定时绑定到IP:0.0.0.0即可,显示本地IP地址的目的是方便对方发送信息过来
- //本地IP的数值形式
- let ipNum = wifiManager.getIpInfo().ipAddress
- //本地IP的字符串形式
- let localIp = (ipNum >>> 24) + '.' + (ipNum >> 16 & 0xFF) + '.' + (ipNum >> 8 & 0xFF) + '.' + (ipNum & 0xFF);
- //服务端ca证书文件地址
- let caFileUri = ''
- //客户端证书文件地址
- let certFileUri = ''
- //客户端私钥文件地址
- let keyFileUri = ''
- @Entry
- @Component
- struct Index {
- //连接、通讯历史记录
- @State msgHistory: string = ''
- //要发送的信息
- @State sendMsg: string = ''
- //服务端IP地址
- @State serverIp: string = "0.0.0.0"
- //服务端端口
- @State serverPort: number = 9999
- //是否可以加载CA
- @State caCanLoad: boolean = false
- //是否已加载CA
- @State caLoaded: boolean = false
- //是否可以加载证书
- @State certCanLoad: boolean = false
- //是否已加载证书
- @State certLoaded: boolean = false
- //是否可以加载私钥
- @State keyCanLoad: boolean = false
- //是否已加载私钥
- @State keyLoaded: boolean = false
- //是否可以发送消息
- @State canSend: boolean = false
- //服务端证书
- @State ca: string = ``
- //客户端证书
- @State cert: string = ``
- //客户端私钥
- @State privateKey: string = ``
- scroller: Scroller = new Scroller()
- build() {
- Row() {
- Column() {
- Text("TLS通讯示例")
- .fontSize(14)
- .fontWeight(FontWeight.Bold)
- .width('100%')
- .textAlign(TextAlign.Center)
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- Text("本地IP地址:")
- .width(100)
- .fontSize(14)
- .flexGrow(0)
- Text(localIp)
- .width(110)
- .fontSize(12)
- .flexGrow(1)
- }.width('100%')
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- Text("服务端地址:")
- .fontSize(14)
- .width(90)
- .flexGrow(1)
- TextInput({ text: this.serverIp })
- .onChange((value) => {
- this.serverIp = value
- })
- .width(110)
- .fontSize(12)
- .flexGrow(4)
- Text(":")
- .width(5)
- .flexGrow(0)
- TextInput({ text: this.serverPort.toString() })
- .type(InputType.Number)
- .onChange((value) => {
- this.serverPort = parseInt(value)
- })
- .fontSize(12)
- .flexGrow(2)
- .width(50)
- }
- .width('100%')
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- Text("服务端CA:")
- .fontSize(14)
- .width(90)
- .flexGrow(1)
- Button("选择")
- .onClick(() => {
- this.selectCA()
- })
- .width(70)
- .fontSize(14)
- Button("加载")
- .onClick(() => {
- this.loadCA()
- })
- .enabled(this.caCanLoad)
- .width(70)
- .fontSize(14)
- }
- .width('100%')
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- Text("客户端证书:")
- .fontSize(14)
- .width(90)
- .flexGrow(1)
- Button("选择")
- .onClick(() => {
- this.selectCert()
- })
- .width(70)
- .fontSize(14)
- Button("加载")
- .onClick(() => {
- this.loadCert()
- })
- .enabled(this.caCanLoad)
- .width(70)
- .fontSize(14)
- }
- .width('100%')
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- Text("客户端私钥:")
- .fontSize(14)
- .width(90)
- .flexGrow(1)
- Button("选择")
- .onClick(() => {
- this.selectKey()
- })
- .width(70)
- .fontSize(14)
- Button("加载")
- .onClick(() => {
- this.loadKey()
- })
- .enabled(this.caCanLoad)
- .width(70)
- .fontSize(14)
- Button("连接")
- .onClick(() => {
- this.connect2Server()
- })
- .enabled(this.caLoaded && this.keyLoaded && this.certLoaded)
- .width(70)
- .fontSize(14)
- }
- .width('100%')
- .padding(10)
- Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
- TextInput({ placeholder: "输入要发送的消息" }).onChange((value) => {
- this.sendMsg = value
- })
- .width(200)
- .flexGrow(1)
- Button("发送")
- .enabled(this.canSend)
- .width(70)
- .fontSize(14)
- .flexGrow(0)
- .onClick(() => {
- this.sendMsg2Server()
- })
- }
- .width('100%')
- .padding(10)
- Scroll(this.scroller) {
- Text(this.msgHistory)
- .textAlign(TextAlign.Start)
- .padding(10)
- .width('100%')
- .backgroundColor(0xeeeeee)
- }
- .align(Alignment.Top)
- .backgroundColor(0xeeeeee)
- .height(300)
- .flexGrow(1)
- .scrollable(ScrollDirection.Vertical)
- .scrollBar(BarState.On)
- .scrollBarWidth(20)
- }
- .width('100%')
- .justifyContent(FlexAlign.Start)
- .height('100%')
- }
- .height('100%')
- }
- //发送消息到服务端
- sendMsg2Server() {
- tlsSocket.send(this.sendMsg + "\r\n")
- .then(async () => {
- this.msgHistory += "我:" + this.sendMsg + await getCurrentTimeString() + "\r\n"
- })
- .catch((e) => {
- this.msgHistory += '发送失败' + e.message + "\r\n";
- })
- }
- //绑定本地地址
- async bind2LocalAddress() {
- //本地地址
- let localAddress = { address: "0.0.0.0", family: 1 }
- await tlsSocket.bind(localAddress)
- .then(() => {
- this.msgHistory += 'bind success' + "\r\n";
- })
- .catch((e) => {
- this.msgHistory += 'bind fail ' + e.message + "\r\n";
- })
- //收到消息时的处理
- tlsSocket.on("message", async (value) => {
- let msg = buf2String(value.message)
- let time = await getCurrentTimeString()
- this.msgHistory += "服务端:" + msg + time + "\r\n"
- this.scroller.scrollEdge(Edge.Bottom)
- })
- }
- //选择CA证书文件
- selectCA() {
- let documentPicker = new picker.DocumentViewPicker();
- documentPicker.select().then((result) => {
- if (result.length > 0) {
- caFileUri = result[0]
- this.msgHistory += "select file: " + caFileUri + "\r\n";
- this.caCanLoad = true
- }
- }).catch((e) => {
- this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
- });
- }
- //选择客户端证书文件
- selectCert() {
- let documentPicker = new picker.DocumentViewPicker();
- documentPicker.select().then((result) => {
- if (result.length > 0) {
- certFileUri = result[0]
- this.msgHistory += "select file: " + certFileUri + "\r\n";
- this.certCanLoad = true
- }
- }).catch((e) => {
- this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
- });
- }
- //选择私钥文件
- selectKey() {
- let documentPicker = new picker.DocumentViewPicker();
- documentPicker.select().then((result) => {
- if (result.length > 0) {
- keyFileUri = result[0]
- this.msgHistory += "select file: " + keyFileUri + "\r\n";
- this.keyCanLoad = true
- }
- }).catch((e) => {
- this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
- });
- }
- //加载CA文件内容
- loadCA() {
- try {
- this.ca = this.readStringFromFile(caFileUri)
- this.caLoaded = true
- }
- catch (e) {
- this.msgHistory += 'readText failed ' + e.message + "\r\n";
- }
- }
- //加载私钥文件内容
- loadKey() {
- try {
- this.privateKey = this.readStringFromFile(keyFileUri)
- this.keyLoaded = true
- }
- catch (e) {
- this.msgHistory += 'readText failed ' + e.message + "\r\n";
- }
- }
- //加载客户端证书文件内容
- loadCert() {
- try {
- this.cert = this.readStringFromFile(certFileUri)
- this.certLoaded = true
- }
- catch (e) {
- this.msgHistory += 'readText failed ' + e.message + "\r\n";
- }
- }
- //从文件读取字符串内容
- readStringFromFile(fileUri: string): string {
- let buf = new ArrayBuffer(1024 * 4);
- let file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
- let readLen = fs.readSync(file.fd, buf, { offset: 0 });
- let result = buf2String(buf.slice(0, readLen))
- fs.closeSync(file);
- return result
- }
- //连接服务端
- async connect2Server() {
- //绑定本地地址
- await this.bind2LocalAddress()
- //服务端地址
- let serverAddress = { address: this.serverIp, port: this.serverPort, family: 1 }
- //tls选项
- let opt: socket.TLSSecureOptions = {
- ca: [this.ca],
- cert: this.cert,
- key: this.privateKey
- }
- await tlsSocket.connect({ address: serverAddress, secureOptions: opt })
- .then(() => {
- this.msgHistory = 'connect success ' + "\r\n";
- this.canSend = true
- })
- .catch((e) => {
- this.msgHistory = 'connect fail ' + e.message + "\r\n";
- })
- }
- }
- //同步获取当前时间的字符串形式
- async function getCurrentTimeString() {
- let time = ""
- await systemDateTime.getDate().then(
- (date) => {
- time = date.getHours().toString() + ":" + date.getMinutes().toString()
- + ":" + date.getSeconds().toString()
- }
- )
- return "[" + time + "]"
- }
- //ArrayBuffer转utf8字符串
- function buf2String(buf: ArrayBuffer) {
- let msgArray = new Uint8Array(buf);
- let textDecoder = util.TextDecoder.create("utf-8");
- return textDecoder.decodeWithStream(msgArray)
- }
复制代码 步调4:编译运行,可以利用模拟器大概真机。
步调5:加载服务端CA、客户端证书、客户端私钥文件,固然必要确保服务端已经启动了,然后配置服务端地址,再单击“连接”按钮执行连接,如图所示:

连接成功后,如果在服务端监听,可以看到如下的连接过程信息:

其中,利用框线标出的即是服务端发送给客户端的认证请求信息以及客户端发送给服务端的证书信息。
步调6:输入要发送的信息,单击“发送”按钮即可发送信息到服务端,如下图所示:

其他鸿蒙开辟文章:
HarmonyOS开辟实战:TLS安全数据传输单向规范-CSDN博客
HarmonyOS开辟实战:邮件传输协议客户端-Smtp-CSDN博客
HarmonyOS开辟实战:邮件传输协议客户端-Smtp-CSDN博客
末了
有很多小伙伴不知道学习哪些鸿蒙开辟技术?不知道必要重点把握哪些鸿蒙应用开辟知识点?而且学习时频仍踩坑,最终浪费大量时间。以是有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
点击领取→纯血版全套鸿蒙HarmonyOS学习资料盼望这一份鸿蒙学习资料能够给各人带来帮助,有必要的小伙伴自行领取~
鸿蒙(HarmonyOS NEXT)最新学习门路
有了门路图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布条记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开辟入门讲授视频,内容包罗:ArkTS、ArkUI、Web开辟、应用模子、资源分类…等知识点。
获取以上完整版高清学习门路,请点击→纯血版全套鸿蒙HarmonyOS学习资料
HarmonyOS Next 最新全套视频教程
《鸿蒙 (OpenHarmony)开辟底子到实战手册》
OpenHarmony北向、南向开辟环境搭建
大厂口试必问口试题
鸿蒙南向开辟技术
鸿蒙APP开辟必备
请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个时机。只有积极应对变化,不断学习和提升自己,他们才气在这个变革的时代中立于不败之地。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |