

简体中文
本文档适用于uni-id 4.0.0
及以上版本,需 HBuilderX 3.5.0 及以上版本。旧版本文档请访问:uni-id 3.x.x 文档
99%的应用,都要开发用户注册、登录、发送短信验证码、修改密码、密码加密保存、密码防探测、token管理、页面访问权限、注册用户统计等众多功能,从前端到后端都需要。
为什么不能有一个开源的通用项目,避免大家的重复开发呢?
uni-id
应需而生。
uni-id
为uniCloud
开发者提供了开源、易用、安全、丰富、可扩展的用户管理框架。
clientDB、DB Schema、uni-starter (opens new window)、uni-admin,这些产品都基于uni-id
的账户体系。可以说uni-id
是uniCloud不可或缺的基础能力。
关于第三点,着重强调下。
一个应用,往往需要集成多个功能模块。比如一个电商应用,需要一个基本电商模板,还需要客服聊天模板、统计看板模板。
在插件市场,每类模板插件都能找到,但他们如果不是基于同一套用户体系设计,就很难整合。
所有uniCloud的标准应用,都基于uni-id
来做。uni-id-common
公共模块自动内置在每个服务空间里的。
有了统一的账户规范,并且围绕这套账户规范,有各种各样插件,那么开发者可以随意整合这些插件,让数据连同。
规范,还可以让上下游充分协同。插件市场会出现各种数据迁移插件,比如把从discuz里把用户迁移到uni-id
中的插件,相信围绕这套规范的产业链会非常活跃。
目前插件市场上各种优秀的uniCloud轮子,几乎都是基于uni-id
的。
uni-id
已完成的功能:
关于登录方式,目前已实现
由于三方登录很多,DCloud没有精力全部实现,在uni-id-co中留下了空实现,欢迎开发者自行补充、提交pr或发布扩展插件,共同完善uni-id
。。
后续计划:DCloud未来将内置 微信扫码登录和公众号登录、邮箱验证集成、facebook等海外主流社交账户登录、活体检测。
其他方面,各种常见开源项目如discuz、wordPress、ecshop的用户导入插件,不属于uni-id
主工程,欢迎开发者单独提交插件到插件市场。
uni-id
贯穿了uni-app前端到uniCloud后端的各个环节。
模块 | 说明 |
---|---|
前端uni-app框架的相关API | uniIdRouter页面路由、token管理客户端API |
前端页面uni-id-pages | 登录、注册、修改密码、忘记密码、个人中心、修改头像等前端页面 |
网络传输自动管理用户token | 自动保存、续期token、网络自动传输token |
云端云对象uni-id-co | 与uni-id-pages搭配的云对象,相关业务的云端部分 |
云端配置uni-config-center | 在uni-config-center下提供各种配置 |
云端公共模块uni-id-common | 用于云函数或云对象集成该模块验证token身份 |
云数据库的用户相关数据表 | uni-id-users等各种opendb数据表 |
uni-admin | Admin管理后台,包括用户角色权限管理、注册用户统计 |
数据库是一个系统的核心,uni-id首先规范化了十几张用户相关的opendb数据表,
其中最为重要的4张opendb表,如下:
主表为uni-id-users
表,保存用户的基本信息。扩展字段有很多,如实名认证数据、工作履历数据。由于MongoDB的特性,开发者可以自由扩展字段。
所有uni-id
的数据表,不管在HBuilderX中新建 DB Schema
还是在 uniCloud web控制台新建表的界面上,都可以选择模板直接建好。
uni-id-common公共模块包含了账户体系服务端的核心权限、token管理,内置在每个uniCloud服务空间里。
如开发者需要在自己的云函数/云对象里校验前端用户token,则需要引用uni-id-common公共模块。
uniCloud众多功能(如DB Schema
的权限、uni-id-co)也都依赖 uni-id-common。
uni-id
在云端有很多配置,比如密码加密秘钥、短信和微信登录的appsecret等等。在uni-config-center
下的uni-id
目录下的config.json里存放着这些配置。
uni-app与uniCloud搭配且使用uni-id,登录后自动下发token、网络传输层自动传输token(uni-app 2.7.13+版本)、token临近过期会自动续期(uni-app 3.4.13 +版本),也就是说开发者无需自己管理token了。
uni-app客户端还有一批uni-id相关的内置API:
基于uni-id-common,DCloud还提供了一组完整的前端页面和后端云对象 ,合称uni-id-pages
。
uni-id-pages的功能包括:用户注册(含用户协议、隐私协议)、退出、修改密码、忘记密码等各种功能,同时适配PC宽屏和各种手机平台(App、H5、小程序)。
此外,DCloud的其他产品也为uni-id提供了众多支持:
以上全部是开源的。
历史遗留
在HBuilderX 3.5之前,DCloud提供了一个公共模块uni-id (opens new window)(注意不叫uni-id-common)和一个示例性云函数uni-id-cf(集成在uni-starter和uni-admin中)。
老的公共模块uni-id是一个大而全的账户管理公共模块,体积太大,不适合被其他云函数引用。比如某个业务云函数需要校验用户token,引用的uni-id公共模块还包含了忘记密码的代码,很浪费资源。
在云对象发布之前,DCloud基于云函数方式提供了uni-id-cf。但在HBuilderX 3.5 以后,推荐使用基于云对象的uni-id-pages,代码更简单清晰。
从HBuilder 3.5起,uni-id (opens new window)和uni-id-cf都将被淘汰,不再更新。老的公共模块uni-id被拆开,变成了uni-id-common公共模块和uni-id-co云对象。
uni-id-common很精简,只包括token和权限,适合被所有云函数引用。
uni-id-co则是一个更加比uni-id-cf更完善和规范的用户管理的云对象。
老版升级指南,详见
uni-id-common的插件市场地址为:uni-id-common插件 (opens new window)。但一般不需要单独下载这个插件,但更新uni-id-common公共模块时需要从这里下载更新。
一般推荐直接使用uni-starter项目模板来开始开发,或者在新项目里导入uni-id-pages页面模板来使用。
uni-id云端的配置是依赖uni-config-center公用模块的,在工程目录uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json。(如未安装uni-config-center需安装,如缺少目录需手动创建)
uni-id云端同时依赖了公共模块uni-captcha (opens new window),这个功能模块负责生成和校验验证码,进行人机验证。
体验uni-id需保证uniCloud服务空间至少有数据表uni-id-users
、opendb-verify-codes
(验证码表)
使用uni-id,首先需要确定2件事:
你的应用采用什么方式注册登录?比如用户名密码、手机号+短信验证码、或者微信登录。
很多登录方式涉及三方服务,需要开通短信验证码服务、开通App一键登录、或者向微信等申请登录的appid和appsecret信息。
申请开通相关服务后,需要把配置信息填写在云端配置文件config.json中。
账户如果涉及密码,那么需要配置passwordSecret
,账户的密码会根据passwordSecret
使用sha1摘要加密算法以不可逆的方式存储在数据库中。
配置tokenSecret
是为了防止token被第三方解密,模拟用户身份。
千万不要使用默认的passwordSecret和tokenSecret,会造成系统安全隐患。
云端的config.json还有各种配置,详见下个章节。前端的配置请参考uni-id-pages的文档。
uni-id的云端配置文件在uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json
中。
注意:
配置项:
passwordSecret
为用于加密密码入库的密钥tokenSecret
为生成token需要的密钥tokenExpiresIn
token有效期,以秒为单位passwordErrorLimit
密码错误重试次数,分ip记录密码错误次数,达到重试次数之后等待passwordErrorRetryTime
时间之后才可以重试passwordErrorRetryTime
单位为秒sendSmsCode
接口发送短信需要前往https://dev.dcloud.net.cn/uniSms (opens new window)充值短信额度,配置config.json
的service
字段,字段说明见下方示例下面的配置文件中所有时间的单位都是秒
!!!重要!!! passwordSecret与tokenSecret十分重要,切记妥善保存(不要直接使用下面示例中的passwordSecret与tokenSecret)。修改passwordSecret会导致老用户使用密码无法登录,修改tokenSecret会导致所有已经下发的token失效。如果重新导入uni-id切勿直接覆盖config.json相关配置
// 如果拷贝此内容切记去除注释
{
"passwordSecret": "passwordSecret-demo", // 数据库中password字段是加密存储的,这里的passwordSecret即为加密密码所用的密钥,注意修改为自己的密钥,使用一个较长的字符串即可
"tokenSecret": "tokenSecret-demo", // 生成token所用的密钥,注意修改为自己的,使用一个较长的字符串即可
"tokenExpiresIn": 7200, // 全平台token过期时间,未指定过期时间的平台会使用此值
"tokenExpiresThreshold": 600, // 新增于uni-id 1.1.7版本,checkToken时如果token有效期小于此值且在有效期内则自动获取新token,请注意将新token返回给前端保存(云对象会自动保存符合uniCloud响应体规范的响应内的新token),如果不配置此参数则不开启自动获取新token功能
"passwordErrorLimit": 6, // 密码错误最大重试次数
"passwordErrorRetryTime": 3600, // 密码错误重试次数超限之后的冻结时间
"autoSetInviteCode": false, // 是否在用户注册时自动设置邀请码,默认不自动设置
"forceInviteCode": false, // 是否强制用户注册时必填邀请码,默认为false
"app": { // 如果你使用旧版本uni-id公共模块而不是uni-id-common这里可能配置的是app-plus,务必注意调整为app
"tokenExpiresIn": 2592000,
"oauth": {
// App微信登录所用到的appid、appsecret需要在微信开放平台获取,注意:不是公众平台而是开放平台
"weixin": {
"appid": "weixin appid",
"appsecret": "weixin appsecret"
},
// App QQ登录所用到的appid、appsecret需要在腾讯开放平台获取,注意:不是公众平台而是开放平台
"qq": {
"appid": "qq appid",
"appsecret": "qq appsecret"
},
"apple": { // 使用苹果登录时需要
"bundleId": "your bundleId"
}
}
},
"web": { // 如果你使用旧版本uni-id公共模块而不是uni-id-common这里可能配置的是h5,务必注意调整为web
"tokenExpiresIn": 14400,
},
"mp-weixin": {
"tokenExpiresIn": 259200,
"oauth": {
// 微信小程序登录所用的appid、appsecret需要在对应的小程序管理控制台获取
"weixin": {
"appid": "weixin appid",
"appsecret": "weixin appsecret"
}
}
},
"mp-qq": {
"tokenExpiresIn": 259200,
"oauth": {
// QQ小程序登录所用的appid、appsecret需要在对应的小程序管理控制台获取
"qq": {
"appid": "qq appid",
"appsecret": "qq appsecret"
}
}
},
"mp-alipay": {
"tokenExpiresIn": 259200,
"oauth": {
// 支付宝小程序登录用到的appid、privateKey请参考支付宝小程序的文档进行设置或者获取,https://opendocs.alipay.com/open/291/105971#LDsXr
"alipay": {
"appid": "alipay appid",
"privateKey": "alipay privateKey", // 私钥
"keyType": "PKCS8" // 私钥类型,如果私钥类型不是PKCS8,需要填写此字段,否则会出现“error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag”错误
}
}
},
"service": {
"sms": {
"name": "your app name", // 应用名称,对应短信模版的name
"codeExpiresIn": 180, // 验证码过期时间,单位为秒,注意一定要是60的整数倍
"smsKey": "your sms key", // 短信密钥key,开通短信服务处可以看到
"smsSecret": "your sms secret", // 短信密钥secret,开通短信服务处可以看到
"scene": {
"bind-mobile": { // 对绑定手机号场景的配置
"templateId": "your template id", // 绑定手机号使用的短信验证码模板
"codeExpiresIn": 240 // 绑定手机号验证码过期时间
}
}
},
"univerify": {
"appid": "your appid", // 当前应用的appid,使用云函数URL化,此项必须配置
"apiKey": "your apiKey", // apiKey 和 apiSecret 在开发者中心获取,开发者中心:https://dev.dcloud.net.cn/uniLogin/index?type=0,文档:https://ask.dcloud.net.cn/article/37965
"apiSecret": "your apiSecret"
}
}
}
关于token自动刷新
tokenExpiresThreshold用于指定token还有多长时间过期时自动刷新token。
例:指定tokenExpiresThreshold:600,tokenExpiresIn:7200
,token过期时间为2小时,在token有效期不足10分钟时自动刷新token
在token还有5分钟过期时调用checkToken接口会返回新的token和新的token的过期时间(新token有效时间也是2小时)。
首先解释下token的概念。token是服务器颁发给客户端的一个令牌。
用户在客户端登录时,云端通过登录接口对用户的用户名+密码,或者手机号+验证码进行校验,校验通过后服务器会给客户端下发一个token(就是根据tokenSecret生成的一串加密字符串),并同时给出有效期。
客户端把这个token保存在storage中,然后每次联网请求服务器时,都带上这个token。服务器解密这个token,通过这个token认定客户端的身份。
这样就避免了客户端每次请求服务器,都需要再传输一次用户名和密码。
这是业内通行的设计。
在传统开发下,客户端和服务器各自需要为了token做很多事情。在uni云端一体下,开发者无需操心,只需要在uni-id云端config.json中配置好token的secret和有效期即可。剩余的工作都被自动处理了。
uni-id云端会在login方法成功后自动返回token,uni-app前端框架会自动识别并保存这个token在storage中(uni-id-token),在前端每次连接uniCloud(不管是clientDB、callfunction、云对象调用),都会自动带上这个token。
云函数和云对象都提供了获取和校验token的方法,在uni-id相关业务中,校验token的代码都已经写好。
包括token快到期时的自动续期,开发者只需在config.json中配置好临近多久自动续期,续期的代码也无法开发者编写,框架已经内置。
注:不同平台的token有效期一般不一样,app有效期较长,web有效期较短。每个平台的有效期都可以单独在config.json里配置。
为什么需要角色权限管理?
uni-id
基于经典的RBAC模型实现了角色权限系统。
RBAC:Role-Based Access Control,基于角色的访问控制。
其基本思想:对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有权限。
这样做的好处是,增强系统管理的扩展性,对于批量用户的权限变更,仅需变更该批用户角色对应权限即可,而无需对该批每个用户变更权限。
这个模型有三个关键名词:用户、角色、权限:
用户、角色、权限都存在数据库了,都可以动态创建和修改。当权限对应的代码实现完成后,用户的新入、退出、角色升迁都无需再修改代码,在uni-admin后台的web界面可以由运维人员可视化的给每个用户调整角色、给每个角色调整权限。
用户信息存储在uni-id-users
表中,然后通过role
字段保存该用户所拥有的所有角色ID,角色ID即角色表(uni-id-roles
表)中的role_id
字段,注意不是_id
字段。
// uni-id-users 表
{
{
"_id":"5f8428181c229600010389f6",
"username":"张三",
"email":"zhangsan@dcloud.io",
"role":[
"USER_ADMIN",
"NOTICE_ADMIN"
],
"created_date":1602495783272
}
}
Tips:将用户角色设计为用户表的字段,而没有新建
用户角色关联表
的原因:避免mongodb在跨表查询时的性能开销
角色信息存储在uni-id-roles
表中
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 系统自动生成的Id |
role_id | String | 是 | 角色唯一标识 |
role_name | String | 否 | 角色名,展示用 |
permission | Array | 是 | 角色拥有的权限列表 |
comment | String | 否 | 备注 |
created_date | Timestamp | 是 | 角色创建时间 |
其中:
role_id
为角色标志,全局唯一,可用于clientDB中的权限控制,建议按照语义化命名,例如:USER_ADMIN
表示人事管理、NOTICE_ADMIN
表示公告管理permission
为数组类型,存储该角色拥有的所有权限ID,权限ID即权限表(uni-id-permissions
表)中的permission_id
字段,注意不是_id
字段如下为示例:
{
{
"_id":"5f8428181c229600010389f6",
"role_id":"USER_ADMIN",
"role_name":"人事管理",
"permission":[
"USER_ADD",
"USER_EDIT",
"USER_DEL"
],
"created_date":1602495783272
},
{
"_id":"5f842836d8daea0001906785",
"role_id":"NOTICE_ADMIN",
"role_name":"公告管理",
"permission":[
"NOTICE_ADD",
"NOTICE_EDIT",
"NOTICE_DEL"
],
"created_date":1602495784372
}
}
如下是角色在clientDB中的配置示例:
// uni-id-users.schema.json
{
"permission": {
"update":"doc._id == auth.uid || 'USER_ADMIN' in auth.role" //用户自己或人事管理员可执行用户表的.update操作
}
}
Tips1:uni-id中
admin
为超级管理员角色,uni-clientDB也基于同样的策略;如果用户角色包含admin
,则该用户就拥有所有数据表的全部权限。
Tips2:出厂时可内置常用角色,也可上线后由运营人员动态创建角色。
权限信息在uni-id-permissions
表中,表结构定义如下:
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 系统自动生成的Id |
permission_id | String | 是 | 权限唯一标识 |
permission_name | String | 否 | 权限名,展示用 |
comment | String | 否 | 备注 |
created_date | Timestamp | 是 | 权限创建时间 |
其中,permission_id
为权限标志,全局唯一,可用于clientDB中的权限配置,建议按照语义化命名,例如:USER_DEL
、BRANCH_ADD
。权限总数量不得超过500
如下为示例内容:
{
{
"_id":"5f8428181c229600010389f6",
"permission_id":"USER_EDIT",
"permission_name":"修改用户",
"created_date":1602495783272
},
{
"_id":"5f842836d8daea0001906785",
"permission_id":"USER_DEL",
"permission_name":"删除用户",
"created_date":1602495784372
}
}
如下是权限在clientDB中的配置示例:
// uni-id-users.schema.json
{
"permission": {
"update":"doc._id == auth.uid || 'USER_EDIT' in auth.permission" //用户自己或有`USER_EDIT`权限的用户,可执行用户表的.update操作
}
}
Tips1:建议代码交付时内置所有权限,方便clientDB中的权限配置和调整。
uni-id将用户的角色权限缓存在token内。详情参考:缓存角色权限。
如下是通过token判断权限的简单示例:
// 简单的权限校验示例
function hasPermission(token, permission) {
const {
uid,
role,
permission
} = await uniID.checkToken(token)
return role.includes('admin') || checkTokenRes.permission.includes(permission) // admin用户的permission为空数组,但是拥有所有权限
}
注意:**在uniCloud admin中,封装了可视化的用户、权限、角色的管理,新增删除修改均支持。**无需自己维护。详见
uni-id
的所有数据表,都在opendb规范中。
在unicloud web控制台 (opens new window) 新建数据表时,可以从uni-id
的模板分类里找到下面的表,并一键创建这些表。HBuilderX 3.4.11起新建 DB Schema 也有模板可选择。
存放用户基本信息。
表名:uni-id-users
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 存储文档 ID(用户 ID),系统自动生成 |
username | String | 否 | 用户名,不允许重复 |
password | password | 否 | 密码。加密存储 |
nickname | String | 否 | 用户昵称 |
gender | int | 否 | 用户性别:0 未知 1 男性 2 女性 |
role | Array | 否 | 用户角色列表,由role_id组成的数组 |
status | int | 是 | 用户状态:0 正常,1 禁用,2 审核中,3 审核拒绝,4 已注销 |
dcloud_appid | Array | 否 | 允许登录的客户端的appid列表,不同应用同时复用一个user表时适用,比如 司机端和乘客端是2个appid,在登陆时可以隔离,见下 |
mobile | String | 否 | 手机号码 |
mobile_confirmed | int | 否 | 手机号验证状态:0 未验证 1 已验证,未验证用户不可登录 |
String | 否 | 邮箱地址 | |
email_confirmed | int | 否 | 邮箱验证状态:0 未验证 1 已验证,未验证用户不可登录 |
avatar | String | 否 | 头像地址 |
wx_unionid | String | 否 | 微信unionid |
wx_openid | Object | 否 | 微信各个平台openid。子结构详见下文 |
qq_unionid | String | 否 | QQ unionid |
qq_openid | Object | 否 | QQ各个平台openid。子结构详见下文 |
ali_openid | String | 否 | 支付宝平台openid |
apple_openid | String | 否 | 苹果登录openid |
comment | String | 否 | 备注 |
realname_auth | Object | 否 | 实名认证信息。子结构详见下文 |
register_date | Timestamp | 否 | 注册时间 |
register_ip | String | 否 | 注册时 IP 地址,uni-id 3.3.14 起移至register_env内 |
last_login_date | Timestamp | 否 | 最后登录时间 |
last_login_ip | String | 否 | 最后登录时 IP 地址 |
login_ip_limit | Array | 否 | 登录 IP 限制 |
inviter_uid | Array | 否 | 邀请人uid,按层级从下往上排列的uid数组,即第一个是直接上级 |
my_invite_code | String | 否 | 用户自己的邀请码 |
register_env | Object | 否 | 用户注册时的环境信息,新增于uni-id 3.3.14 。子结构详见下文 |
注意
wx_openid字段定义
opendb中uni-id-users表1.0.0调整为下面的结构,uni-id-co使用此标准。如何处理旧数据请参考:自uni-id升级为uni-id-co+uni-id-common
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
app | String | 否 | app平台微信openid |
mp | String | 否 | 微信小程序平台openid |
web | String | 否 | 微信网页应用openid |
h5 | String | 否 | 微信公众号应用openid |
qq_openid字段定义
opendb中uni-id-users表1.0.0调整为下面的结构,uni-id-co使用此标准。如何处理旧数据请参考:自uni-id升级为uni-id-co+uni-id-common
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
app | String | 否 | app平台QQ openid |
mp | String | 否 | QQ小程序平台openid |
realNameAuth 扩展字段定义 该字段存储实名认证信息,子节点说明如下。
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
type | Integer | 是 | 用户类型:0 个人用户 1 企业用户 |
auth_status | Integer | 是 | 认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败 |
auth_date | Timestamp | 否 | 认证通过时间 |
real_name | String | 否 | 真实姓名/企业名称 |
identity | String | 否 | 身份证号码/营业执照号码 |
id_card_front | String | 否 | 身份证正面照 URL |
id_card_back | String | 否 | 身份证反面照 URL |
id_card_in_hand | String | 否 | 手持身份证照片 URL |
license | String | 否 | 营业执照 URL |
contact_person | String | 否 | 联系人姓名 |
contact_mobile | String | 否 | 联系人手机号码 |
contact_email | String | 否 | 联系人邮箱 |
register_env字段定义
注意:该字段是在前端注册用户时记录的前端环境信息。如果是管理员在云端调用uni-id的addUser添加的用户则无此字段
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
appid | String | 否 | 注册时的客户端appId |
uni_platform | String | 否 | 注册时的客户端平台,如h5、app、mp-weixin等 |
os_name | String | 否 | 注册时的客户端系统名,ios、android、windows、mac、linux |
app_name | String | 否 | 注册时的客户端名称 |
app_version | String | 否 | 注册时的客户版本 |
app_version_code | String | 否 | 注册时的客户版本号 |
channel | String | 否 | 注册时的客户端启动场景(小程序)或应用渠道(app) |
client_ip | String | 否 | 注册时的客户端IP |
用户集合示例:
{
"_id": "f2a60d815ee1da3900823d45541bb162",
"username": "姓名"
"password": "503005d4dd16dd7771b2d0a47aaef927e9dba89e",
"status":0,//用户状态:0正常 1禁用 2审核中 3审核拒绝
"mobile":"",
"mobile_confirmed":0, //手机号是否验证,0为未验证,1为已验证
"email":"amdin@domain.com",
"email_confirmed":0, //邮箱是否验证,0为未验证,1为已验证
"avatar":"https://cdn.domain.com/avatar.png"
"last_login_ip": "123.120.11.128", //最后登录IP
}
目前 opendb 内提供的 uni-id-users表 包含完整的索引,数据库在索引量多且频繁更新的情况下可能会出现写入缓慢的情况,因此推荐开发者在使用 uni-id-users表 时可以删除没有用到的索引。
例:项目内只使用了微信登录,不使用其他登录方式,可以只保留wx_unionid、wx_openid.mp
这些账号相关的索引,删除其他登录方式的索引(比如username、mobile)
不了解索引请参考:索引
表名:opendb-verify-codes
该表的前缀不是uni-id,意味着该表的设计用途是通用的,不管是uni-id的手机号验证码,或者支付等关键业务需要验证码,都使用此表。
每条验证信息,都记录在本表中。uni-id不会自动删除本表的历史数据,数据保留有效期需要开发者自行管理,可以在云函数中设置一个定时运行来清理过期数据。
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 存储文档 ID(验证码 ID),系统自动生成 |
mobile | String | 是 | 手机号,和邮箱二选一 |
String | 是 | 邮箱,和手机号二选一 | |
code | String | 是 | 验证码 |
scene | String | 是 | 验证场景 |
state | Integer | 是 | 验证状态:0 未验证 1 已验证 2 已作废 |
ip | String | 是 | 请求时 IP 地址 |
create_date | Timestamp | 是 | 创建时间 |
expired_date | Timestamp | 是 | 验证码过期时间 |
表名:uni-id-roles
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 系统自动生成的Id |
role_id | String | 是 | 角色唯一标识 |
role_name | String | 否 | 角色名,展示用 |
permission | Array | 是 | 角色拥有的权限列表 |
comment | String | 否 | 备注 |
created_date | Timestamp | 是 | 角色创建时间 |
表名:uni-id-permissions
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
_id | Object ID | 是 | 系统自动生成的Id |
permission_id | String | 是 | 权限唯一标识 |
permission_name | String | 否 | 权限名,展示用 |
comment | String | 否 | 备注 |
created_date | Timestamp | 是 | 权限创建时间 |
还有更多uni-id的配套数据表,可以在uniCloud web控制台 (opens new window)新建表时选择相应模板。此处不再详述,仅罗列清单:
对应uni-id-users
表的status字段
值 | 说明 |
---|---|
0 | 正常状态 |
1 | 封禁状态 |
2 | 审核中 |
3 | 审核失败 |
4 | 注销状态 |
对应uni-id-log
表的type字段
值 | 说明 |
---|---|
logout | 登出 |
login | 登录 |
register | 注册 |
reset-pwd | 重置密码 |
bind-mobile | 绑定手机 |
bind-weixin | 绑定微信账号 |
bind-qq | 绑定QQ账号 |
bind-apple | 绑定苹果账号 |
bind-alipay | 绑定支付宝账号 |
对应opendb-verify-codes
表短信、邮箱验证码相关记录的的scene字段
值 | 说明 |
---|---|
login-by-sms | 短信验证码登录 |
reset-pwd-by-sms | 短信验证码重置密码 |
bind-mobile-by-sms | 短信验证码绑定手机号 |
对应opendb-verify-codes
表图形验证码相关记录的scene字段
值 | 说明 |
---|---|
login-by-pwd | 用户名/手机/邮箱+密码登录 |
login-by-sms | 短信验证码登录 |
reset-pwd-by-sms | 短信验证码重置密码 |
send-sms-code | 发送短信验证码 |
bind-mobile-by-sms | 短信验证码绑定手机号 |
新增于 HBuilderX 3.5.0
uniIdRouter 是一个运行在前端的、对前端页面访问权限路由进行控制的方案。
大多数应用,都会指定某些页面需要登录才能访问。以往开发者需要写不少代码。
现在,只需在项目的pages.json
内配置登录页路径、需要登录才能访问的页面等信息,uni-app框架的路由跳转,会自动在需要登录且客户端登录状态过期或未登录时跳转到登录页面。
结合以下代码及注释了解如何使用uniIdRouter
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
},
"needLogin": false // 当前页面是否需要登录才可以访问,此配置优先级高于uniIdRouter下的needLogin
}, {
"path": "pages/list/list",
"style": {
"navigationBarTitleText": "uni-app"
},
"needLogin": true
}, {
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {
"loginPage": "pages/index/index", // 登录页面路径
"needLogin": [
"pages/detail/.*" // 需要登录才可访问的页面列表,可以使用正则语法
],
"resToLogin": true // 自动解析云对象及clientDB的错误码,如果是客户端token不正确或token过期则自动跳转配置的登录页面,配置为false则关闭此行为,默认true
}
}
以上代码,指定了登录页为首页index
,然后将list
页面和detail
目录下的所有页面,设为需要登录才能访问。那么访问list
页面和detail
目录下的页面时,如果客户端未登录或登录状态过期(也就是uni-id-token失效),那么会自动跳转到index
页面来登录。
与此功能对应的有两个uniCloud客户端api,uniCloud.onNeedLogin()
和uniCloud.offNeedLogin()
,开发者在监听onNeedLogin事件后,框架就不再自动跳转到登录页面,而是由开发者在onNeedLogin事件内自行处理。详情参考:uniCloud.onNeedLogin
自动跳转到登录页面时会携带uniIdRedirectUrl参数,其值为encodeURIComponent(${跳转前的页面(包含路径和参数的完整页面地址)})
,如果希望用户登录后跳转回之前的页面,可以使用此参数实现。
以下为登录页面跳转到之前访问页面的简单示例:
pages/login/login.vue
<template>
<view>
<button @click="login">login</button>
</view>
</template>
<script>
export default {
data() {
return {
uniIdRedirectUrl: ''
}
},
onLoad(options) {
this.uniIdRedirectUrl = decodeURIComponent(options.uniIdRedirectUrl)
},
methods: {
async login() {
// ...执行登录操作,在成功回调里跳转页面
if (this.uniIdRedirectUrl) {
uni.redirectTo({
url: this.uniIdRedirectUrl
})
}
}
}
}
</script>
云对象抛出uni-id token过期或token无效错误码时,会触发客户端自动跳转配置的登录页面,以下代码为一个简单示例
// todo云对象
const uniIdCommon = require('uni-id-common')
module.exports = {
_before(){
this.uniIdCommon = uniIdCommon.createInstance({
clientInfo: this.getClientInfo()
})
},
addTodo(title) {
const {
errCode,
errMsg,
uid
} = await this.uniIdCommon.checkToken(this.getUniIdToken())
if(errCode) { // uni-id-common的checkToken接口可能返回`uni-id-token-expired`、`uni-id-check-token-failed`错误码,二者均会触发客户端跳转登陆页面
return {
errCode,
errMsg
}
}
// ...
}
}
客户端add-todo.vue
<template>
<!-- 略 -->
</template>
<script>
export default {
data() {
return {
}
},
onLoad() {},
methods: {
async addTodo(title){
const todo = uniCloud.importObject('todo')
await todo.addTodo(title) // 调用addTodo时云端checkToken如果返回了token错误、token失效的错误码就会自动跳转到配置的登录页面
}
}
}
</script>
<style>
</style>
注意
uniIdRouter
节点上述逻辑才会生效,自HBuilderX 3.5.0起创建空项目模板会自动配置空的uniIdRouter
节点错误码errCode | 错误信息errMsg | 说明 |
---|---|---|
0(数字) | 成功 | - |
uni-id-token-expired | 登陆状态失效,token已过期 | - |
uni-id-check-token-failed | token校验未通过 | - |
uni-id-account-exists | 账户已存在 | - |
uni-id-account-not-exists | 账户不存在 | - |
uni-id-account-conflict | 用户账号冲突 | 可能会由开发者手动更新数据库导致,正常情况下不应出现 |
uni-id-account-banned | 此账号已封禁 | - |
uni-id-account-auditing | 此账号正在审核中 | - |
uni-id-account-audit-failed | 此账号审核失败 | - |
uni-id-account-closed | 此账号已注销 | - |
uni-id-captcha-required | 请输入图形验证码 | - |
uni-id-password-error | 用户名或密码错误 | - |
uni-id-invalid-username | 用户名不合法 | - |
uni-id-invalid-password | 密码不合法 | - |
uni-id-invalid-mobile | 手机号码不合法 | - |
uni-id-invalid-email | 邮箱不合法 | - |
uni-id-invalid-nickname | 昵称不合法 | - |
uni-id-invalid-param | 参数错误 | - |
uni-id-param-required | 缺少参数 | - |
uni-id-get-third-party-account-failed | 获取第三方账号失败 | - |
uni-id-get-third-party-user-info-failed | 获取第三方用户信息失败 | - |
uni-id-mobile-verify-code-error | 手机验证码错误或已过期 | - |
uni-id-email-verify-code-error | 邮箱验证码错误或已过期 | - |
uni-id-admin-exists | 超级管理员已存在 | - |
uni-id-permission-error | 权限错误 | - |
uni-id-system-error | 系统错误 | - |
uni-id-set-invite-code-failed | 设置邀请码失败 | - |
uni-id-invalid-invite-code | 邀请码不可用 | - |
uni-id-change-inviter-forbidden | 禁止修改邀请人 | - |
uni-id-bind-conflict | 此账号(微信、QQ、手机号等)已被绑定 | - |
有些系统由多个子应用组成,且没有各自独立服务空间,而是需要共享一个服务空间。此时就涉及一个问题,多个应用注册的账户都在uni-id-user表中,如何有效隔离。
比如一个打车软件,有乘客端、司机端、管理端,都要注册账户。它们也都有自己的DCloud appID(manifest.json里第一个配置)
uni-id-user表中有一个数组型字段dcloud_appid
,可以存贮这个用户有权登陆哪个应用。
比如乘客端的appid是__uni_111111
,司机端appid是__uni_222222
,那么2个appid都存入dcloud_appid
,即表示这个用户有权登录这2个应用。
uni-id 3.3.0版本起用户注册时会自动在用户表的记录内标记为注册应用对应的用户,如果没有单独授权登录其他应用的话则只能登录这个应用。即在乘客端应用注册的,默认只能在乘客端应用登录。
如何授权登录其他应用请参考:授权、禁止用户在特定客户端应用登录
需要注意的是客户端APPID信息是由端上传上来的,并非完全可信,尽量在入口处进行校验。例:
exports.main = async function(event, context){
if(context.APPID !== '__UNI__xxx1') {
throw new Error('应用ID非法')
}
}
DCloud Appid是一个很重要的配置,如无必要请勿随意更换。
不同端用户数据通过用户表的dcloud_appid字段隔离,同一个手机号、微信号也可以同时注册管理端和用户端,绑定账号同理。
注意
dcloud_appid
字段(此字段是一个数组,标识此用户可以在哪些端登录)。dcloud_appid
字段是一个空数组,表示当前用户不能在任何客户端登录uni-id的config.json支持配置为数组,每项都是一个完整的配置,对不同的配置使用dcloudAppid
字段进行区分(此字段与项目内的manifest.json里面的DCloud AppId一致),
uni-id会自动根据客户端的appid来判断该使用哪套配置。
需要注意的是客户端APPID信息是由端上传上来的,并非完全可信,尽量在入口处进行校验。例:
exports.main = async function(event, context){
if(context.APPID !== '__UNI__xxx1') {
throw new Error('应用ID非法')
}
}
示例
数组每一项都是一个完整的配置文件,全部选项请参考:uni-id 配置
注意:如果允许同一账号在不同端使用相同的账号+密码登录需要将不同端的passwordSecret设置成一样的
[{
"dcloudAppid": "__UNI__xxxx1", // 务必替换为对应项目manifest.json内的DCloud Appid
"isDefaultConfig": true, // 默认配置标记,未匹配到dcloudAppid的情况下使用默认配置
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "tokenSecret-demo",
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600
}, {
"dcloudAppid": "__UNI__xxxx2", // 务必替换为对应项目manifest.json内的DCloud Appid
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "tokenSecret-demo",
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600
}]
在config.json
(uniCloud/cloudfuntions/common/uni-config-center/uni-id/config.json
,以下config.json均指此文件)内配置了autoSetInviteCode: true
则在用户注册时会自动给设置不重复的6位邀请码
在config.json
内配置了forceInviteCode: true
则只有使用邀请码才可以注册。
uni-id-co
在会产生注册行为的接口均添加了inviteCode参数,用于传递邀请码使注册用户接受邀请
注意:通常情况下设定好passwordSecret之后不需要再进行修改,使用此功能时请务必小心谨慎
说明
在config.json内修改passwordSecret会导致历史用户无法通过密码登录。但是某些情况下有些应用有修改passwordSecret的需求,例如刚开始使用uni-id时没有自定义passwordSecret,后续需要修改,此时可以使用uni-id 2.0.1版本新增的修改passwordSecret功能。(注意:2.0.1版本验证码表名调整为了opendb-verify-codes
)
如何使用
下面以将passwordSecret从passwordSecret-demo
修改为qwertyasdfgh
为例介绍如何使用
// 旧config.json
{
"passwordSecret": "passwordSecret-demo"
}
// 新config.json
{
"passwordSecret": [{
"version": 1,
"value": "passwordSecret-demo"
},{
"version": 2,
"value": "qwertyasdfgh"
}]
}
如果在上面基础上再修改passwordSecret为1q2w3e4r5t
,config.json调整如下
!!!注意只有在数据库内完全没有使用某个版本(
password_secret_version
字段表示了用户密钥版本)密钥的用户才可以将此密钥从config.json内去除。没有password_secret_version
的用户使用的是最旧版本的passwordSecret,如果存在这样的用户对应的passwordSecret也不可去除。
// 新config.json,
{
"passwordSecret": [{
"version": 1,
"value": "passwordSecret-demo"
},{
"version": 2,
"value": "qwertyasdfgh"
},{
"version": 3,
"value": "1q2w3e4r5t"
}]
}
原理
uni-id-users表内存储的password字段为使用hmac-sha1生成的hash值,此值不可逆向推出用户真实密码。所以直接修改passwordSecret会导致老用户无法使用密码登录。
上述修改通过密钥版本号区分新旧密钥,用户登录时如果密钥版本小于当前最新版本,会为用户更新数据库内存储的password字段,并记录当前使用的密钥版本。
用户对应的数据库记录内没有密钥版本的话会使用最低版本密钥进行密码校验,校验通过后为用户更新为最新版密钥对应的password并记录版本号。
由于是不可逆加密,理论上passwordSecret泄露不会造成用户的真实密码被泄露,自定义passwordSecret只是进一步加强安全性。
使用uni-id-common
时,token内会缓存用户的角色权限。
为什么要在token缓存角色权限?token校验是高频操作,云数据库是按照读写次数来收取费用的,并且读写数据库会拖慢接口响应速度。
比较经济的做法就是在token里缓存角色权限。更好的方案是在redis里缓存角色权限,只是redis需要付费开通。
注意
uni-id-common支持在token内缓存用户的角色权限。但是某些情况下开发者可能还希望缓存一些别的东西,以便在客户端能方便的访问(注意:不可缓存机密信息到token内)。
用法
在uni-config-center
模块内的uni-id插件内创建custom-token.js
内容如下:
module.exports = async (tokenObj) => {
// tokenObj为原始token信息结构如下
// {
// uid: 'abc', // 用户id
// role: [], // 用户角色列表
// permission: [] // 用户权限列表,admin角色的用户权限列表为空数组
// }
tokenObj.customField = 'hello custom token' // 自定义token字段
return tokenObj // 注意务必返回修改后的token对象
}
uni-id会自动加载custom-token.js进行处理,在所有生成token的操作(包括:登录、注册、token过期自动刷新、开发者自行调用createToken)执行时自动获取新token信息,并生成token。
注意
完整词句列表参考:
用法
在cloudfunctions/common/uni-config-center/uni-id/lang/
目录下创建index.js
,内容示例如下:
module.exports = {
'zh-hant': { // 语言代码
'alipay': '支付寶'
}
}
uni-id会自动进行语言匹配,无需额外配置