May 22, 2021 Mini Program Cloud Development Advanced
Anonymous sign-in allows users to use databases, cloud storage, and call cloud functions for a short period of time without registering to sign in, but most applications need to acquire the user's identity to store data in the cloud more securely over the longer term and to get a consistent experience across devices. On the Web side we can use custom logins combined with anonymous logins, and on the WeChat side, with cloud development, you can log on without the need for additional operations (in fact, openId), and to achieve cross-end agreement, you need to consider the combination of rights-free logins and custom logins.
WeChat small program cloud development has a set of no-access account system openid, we can be based on this account system combined with custom login to achieve the Web side and small terminal cross-terminal login and permission control.
For the convenience of learning, let's assume (or simulate) that the user has already used our applet and left userId and openid, open the applet cloud development database to create a new collection in the database, the name of the collection is user, and create a new record in the user, for example:
{
_openid:"oUL-m5FuRmuVmxvbYOGnXbnEDsn8",
userId:"lidongbbsky",
}
Since the small program is using cloud development, the user can call the resources in the cloud development environment without registering to log on, so when this user to the Web page, how should they log on to the previous account? Simply enter userId on the web page to sign in.
Direct input above this userId can log in without entering a password, this is an unsafe practice, but the security practice does not necessarily require a password, we can use the cloud function every ten seconds dynamic refresh userId to replace the traditional way of user name and password, such as userId for the last three digits of the openid life cycle of only 10 seconds dynamic three digits (a bit similar to the dynamic verification code of text messages), T he user userId can only be obtained by logging on to a small program, so that the user only needs to enter 6 digits, which is convenient and safe. Of course you can also generate userId in other ways.
Through the database, we associate the userId with the only openid of the small program, so how do we implement the userId login on the web page? And how to ensure the security of login?
Open tencent cloud development web console, in the "environment" - "environment settings" - "login method", click the private key download, the private key is a JSON format data, which
private_key_id
private_key
Next, we'll use the cloud function to generate a unique user ID (called customUserId) in conjunction with this private key file to calculate the cloud-developed custom login credentials ticket, and finally use ticket login.
Then use VS Code to create a new cloud function, such as the weblogin cloud function, which is designed to handle the login of a Web page, and customize the name of the private key json file, such as tcb_custom_login.json, to the directory with the cloud function.
├── weblogin //weblogin云函数目录
│ └── index.js
│ └── config.json
│ └── package.json
│ └── tcb_custom_login.json //下载的私钥json文件
Then enter the following code .js the index code to create a service that generates the ticket, and the logic of the code is as follows:
const tcb = require('@cloudbase/node-sdk')
const app = tcb.init({
env: 'xly-xrlur',
credentials: require('./tcb_CustomLoginKeys.json')
})
const db = tcb.database();
exports.main = async (event, context) => {
const userId = event.queryStringParameters.userId //从web端传入的userId
try{
if( userId != null){ //如果web端传入的userId非空,就从数据库查询是否存在该userId
const users = (await db.collection('users').where({
userId:userId
}).get()).data
if(users.length != 0){ //当数据库存在该userId时,users为一个数组,数组长度不为0
//使用用户的openid为customUserId来生成ticket,因为openid有一个-连接符,把它给替换掉
const customUserId = await (users[0]._openid).replace('-','')
const ticket = app.auth().createTicket(customUserId, {
refresh: 10 * 60 * 1000 // 每十分钟刷新一次登录态, 默认为一小时
});
return {
statusCode: 200,
headers: {
'content-type': 'application/json',
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'*',/=
'Access-Control-Allow-Headers':'Content-Type'
},
body: ticket
}
}
}
}catch(err){
console.log(err)
}
}
After uploading the weblogin cloud function deployment, then turning on cloud access (HTTP trigger) and creating routes such as /weblogin, we can enter the following address in the browser (i.e., the value of the incoming parameter userId in weblogin cloud access is lidongbbsky) to get the generated ticket:
http://xly-xrlur.service.tcloudbase.com/weblogin?userId=lidongbbsky
We've generated a ticket using the cloud function, so how does the front end log in based on that ticket? We still use axios for HTTP requests, so we introduce axios first in our front-end pages, such as .html under the public folder
<script src="https://imgcache.qq.com/qcloud/tcbjs/1.5.1/tcb.js" rel="external nofollow" ></script>
<script src="https://unpkg.com/axios/dist/axios.min.js" rel="external nofollow" ></script>
<script src="./js/main.js"></script>
Then enter the following code in the page lifecycle function
window.onload= function(){//生命周期函数}
lifecycle function) in the main .js, first return the user's login loginState to determine whether the user has logged in, and if the user is not logged in, initiate an HTTP request, get the ticket returned by the cloud access, and then use
auth.customAuthProvider().signIn(ticket)
log in to the cloud with custom login credentials:
const auth = app.auth({
persistence: 'session' //在窗口关闭时清除身份验证状态
})
async function login(){
const loginState = app.auth().hasLoginState();
if(!loginState){
const url ="https://xly-xrlur.service.tcloudbase.com/weblogin"
axios.get(url,{
userId:"lidongbbsky"
})
.then(res => {
auth.customAuthProvider()
.signIn(res.data)
.then(() => {
console.log("登录成功")
//登录成功后,就可以操作云开发环境里的各种资源啦
})
.catch(err => {
console.log("登录失败",err)
});
}).catch(err => {
console.log(err)
})
}else{
console.log("您已经登录啦")
}
}
login()
When we log on to the web side, the web user will also have an openid similar to a small program (but not the same), so how do we get this openid? Like small program users, when we add data to cloud storage and databases, we automatically add an openid field with a value of the web-side openid.
So beyond that, can we get the openid of the web user in the cloud function like small program cloud development? This is actually the webtest cloud function that we've already written in the previous web cloud development, and it's taken out separately here:
const tcb = require('@cloudbase/node-sdk')
const app = tcb.init({
env: 'xly-xrlur'
})
const auth = app.auth()
exports.main = async (event, context) => {
const {openId, uid, customUserId } = auth.getUserInfo()
return {openId, uid, customUserId }
}
The uid here is the openid of the web user, while the openId is the openid of the WeChat user (small program), and customUserId is the customUserId that we used to generate the ticket.
When the user uses customUserId custom login on the web side, there will also be a different from the small program account system openid, this openid is the user's uid, customUserId and uid are corresponding, as long as the customUserId does not change, the web user's openid (uid) will not change. That is, since our customUserId is unique to the openid of a small program and does not change with the device over time, the openid (uid) on the web side does not change because of the device and time.
Although the user in the web side of the user can be dynamically refreshed, but in the cloud function we do not have this can be dynamically refreshed userId as customUserId, so don't worry about the difference between user Id, web user development in the cloud openid will change;
The uniqueness of the web-side user's openid (i.e. uid) and its perennity, which does not change with device and time, are the basis for our cross-device operations. However, it is worth mentioning that even if the same user web side of the openid and the small program of openid, although related, but the two are different account systems, if we want to make the small program and the web side of the account need to be processed.
Even for the same users, openids on the web side and on the small terminal are unique and permanent, and are different, what if the same users have a consistent experience and the same permissions on the Web side and on the small terminal? W e know that the permissions of cloud development are very dependent on openid, whether it is the addition and deletion of the database, or the addition and deletion of cloud storage, are based on openid to judge the user's permissions. Account system may be easier to get through, but how can permissions be controlled?
For example, the user created a profile on the small terminal, published an article, we want to open the account, we have to enable the user on the web side can view and can modify his profile or article data, such as the following is a record in the user collection:
{
_openid:"oUL-m5FuRmuVmxvbYOGnXbnEDsn8",
userId:"lidongbbsky",
userInfo:{
name:"李东bbsky",
title:"杂役"
},
posts:[{
title:"为什么说云开发值得普及?",
content:"<h3>学习门槛特别低</h3><p>可以说云开发是最容易上手且最容易出成果的编程方向了</p>"
}]
}
When we set the collection
所有人可读,仅创建者可读写
to all, and only the creator to read and write, the user can read and write to his own records on the small terminal, but when the user is on the web side, he can only read and
not write, unless the cloud function is used to query the user's openid in the database first (if you design userId to refresh dynamically) Then the database and storage addition and deletion check, that is, the user of the database and cloud storage of all operations need to go through the cloud function and all need to query the user in the small terminal
openid, although the function can be implemented,
but the web side is not very friendly, one is more than one query, the other is not directly on the web side of the write operation.
If you
want to make it easier for
users on the web side to
get through user rights on the smaller side without the need for cloud functions, you need to rely on security rules, such as the
仅创建者可读写
readable and writeable security rules:
{
"read": "auth.openid == doc._openid",
"write": "auth.openid == doc._openid"
}
This security rule gives the user of a small program read and write _openid the same value as the recorded value of the field. T
hat
auth.openid
the openid after the login-free user of the applet. S
o how do web users have the same permissions? W
e can add a webuid field to each record of user to record the openid of the web user and a wxuid field to record the openid of the small terminal.
There are four situations in which permissions are interoperable:
auth.openid == doc._openid
the small program user has read and write rights to the record;
auth.uid == doc.webuid
that the webd user can have read and write permissions on the record;
auth.uid == doc._openid
the web user has read and write rights to the record;
auth.openid == doc.wxuid
that the webd user can have read and write permissions on the record;
So, we can set the security rules to the following, whether the record is created on the small terminal or on the web side, and the user has readable and write permissions across the end:
{
"read": "auth.openid == doc._openid || auth.uid == doc.webuid || auth.uid == doc._openid || auth.openid == doc.wxuid",
"write": "auth.openid == doc._openid || auth.uid == doc.webuid || auth.uid == doc._openid || auth.openid == doc.wxuid",
}
The reason is so complex, because the web side when creating records _openid is the user's uid, the small terminal when creating records _openid is WeChat ecological _openid, and to do two sets of systems easy, we need a field to do the transition, we can also use only one field, such as only one uid field, when the record _openid is the small program's _openid, uid records the web user's uid; When openid is the user's uid on the web side, the uid records the user's openid in the applet, and the security rules can be written as:
{
"read": "auth.openid == doc._openid || auth.uid == doc.uid || auth.uid == doc._openid || auth.openid == doc.uid",
"write": "auth.openid == doc._openid || auth.uid == doc.uid || auth.uid == doc._openid || auth.openid == doc.uid"
}