PTAP01 兼容DID的自主身份标识应用接口定义(草案 20201225) 一、兼容oAuth的第三方验证实现流程 参考传统oAuth验证流程,通过使用第三方提供的ODIN身份验证服务,应用前端代码实现上非常简单。 应用先选择一个开放的奥丁号认证服务节点进行登记, 如 国际节点 https://tool.ppkpub.org/oauth/ 中国节点 https://ppk001.sinaapp.com/oauth/ 也可以使用源码自行搭建认证服务节点。 获得后面调用oAuth协议需要的应用标识(client_id) 和 认证密钥 (client_secret)。 下面以最常用的oAuth授权码模式(authorization code)作为示例,其它验证模式可以按照oAuth标准定义参考实现。 1.1 给已注册用户关联奥丁号 假设你的用户属性显示网址为: https://www.YourSite.com/userinfo.html 参考实现步骤如下: (A)在用户属性显示网页,建议增加显示“关联奥丁号”一栏和相应的修改按钮,点击“修改”按钮将页面导向到所选的认证服务提供者的地址, 例如: https://tool.ppkpub.org/oauth/authorize.php?response_type=code&client_id=xxxxxxxx&state=userinfo&redirect_uri=https://www.YourSite.com/userinfo.html 注: 注: state字段取值可以自行填写,能自行区分不同的应用功能即可。 (B)用户给予授权,认证服务器将用户导向到指定的”重定向URI”(redirection URI),同时附上一个授权码(code),如下所示: https://www.YourSite.com/userinfo.html?code=xxxxxxxx (C)应用的前端页面解析网址参数,判断收到授权码时,以AJAX方式通过应用的后台API服务器,向奥丁号认证服务节点确认新关联的奥丁号,并更新显示。 应用后端服务器的API接口输入参数: code: 上一步获得的授权码 redirect_uri: 对应请求时指定的”重定向URI”, 如 https://www.YourSite.com/userinfo.html 输出结果: status: 成功取值OK,其他取值 ERROR user_odin_uri: 成功时返回新关联的奥丁号,前端页面相应更新显示 name: 可选的名称字符串 avatar: 可选的头像URL ... //可选的更多扩展字段,由具体标识服务来自行定义 注:如果前端页面不是静态HTML,而是采用PHP,node.js等动态运行产生,也可以省略AJAX方式,直接由后端实现验证并显示结果网页。 应用后端服务器的具体实现参考: (1)将类似如下键值数组 "grant_type" : "authorization_code", "code" : "上一步获得的授权码(code)", "client_id" : "登记获得的应用标识", "client_secret" : "登记获得的认证密钥", "redirect_uri" : "https://www.YourSite.com/userinfo.html" 以表单形式POST到 https://tool.ppkpub.org/oauth/token.php 授权认证服务器核对了授权码和重定向URI,确认无误后,向应用提供访问令牌(access token)和更新令牌(refresh token) 应答为json格式,示例如下: { "access_token":"0bf3ea9f460ebb770e53f2795392d88afd878000", "expires_in":3600, "token_type":"Bearer", "scope":null, "refresh_token":"f953d5fd6d4ed0fa83c20afe2fcd5a1b74374ec6" } 判断是否存在 access_token字段有效取值,是则正常处理,否则按出错处理。 (2)使用访问令牌(access token)从认证服务器获得对应获得授权的用户资料信息(如奥丁号,昵称和头像图片网址等) 例如: https://tool.ppkpub.org/oauth/resource.php?client_id=xxxxxxxx&state=userinfo&access_token=上一步获得的访问令牌 所发出的GET请求里的字段取值建议如下: content_type: text/html;charset=UTF-8 accept-encoding: * 应答为json格式,示例如下: { "success":true, "userInfo":{ "user_odin_uri" : "ppk:12345*", "name":"说明:可选的标识名称字符串", "avatar":"说明:可选的头像URL", ... //可选的更多扩展字段,由具体标识服务来自行定义 } } 当success为true时按正常处理,其他情况都按出错处理,具体可打印应答信息查看。 (3) 将新的user_odin_uri等信息更新保存到数据库里的对应用户记录,然后返回应答: 1.2 实现用奥丁号登录 假设你的用户登录网址为: https://www.YourSite.com/login.html 参考实现步骤如下: (A)用户访问应用的登录网页,后者将前者导向选定的认证服务提供者的地址, 例如:https://tool.ppkpub.org/oauth/authorize.php?response_type=code&client_id=xxxxxxxx&state=login&redirect_uri= https://www.YourSite.com/login.html 注: state字段取值可以自行填写,能自行区分不同的应用功能即可。 (B)用户给予授权,认证服务器将用户导向到指定的”重定向URI”(redirection URI),同时附上一个授权码(code),如下所示: https://www.YourSite.com/login.html?code=xxxxxxxx (C)应用的前端页面解析网址参数,判断收到授权码时,以AJAX方式通过应用的后台API服务器,向奥丁号认证服务节点登录的奥丁号及所关联的用户。 应用后端服务器的API接口输入参数: code: 上一步获得的授权码 redirect_uri: 对应请求时指定的”重定向URI”, 如 https://www.YourSite.com/login.html 输出结果: status: 成功取值OK,出错取值 ERROR 注:如果前端页面不是静态HTML,而是采用PHP,node.js等动态运行产生,也可以省略AJAX方式,直接由后端实现验证并显示结果网页。 应用后端服务器的具体实现参考: (1)将类似如下键值数组 "grant_type" : "authorization_code", "code" : "上一步获得的授权码(code)", "client_id" : "初始登记获得的应用标识", "client_secret" : "初始登记获得的认证密钥", "redirect_uri" : " https://www.YourSite.com/login.html" 以表单形式POST到 https://tool.ppkpub.org/oauth/token.php 授权认证服务器核对了授权码和重定向URI,确认无误后,向应用提供访问令牌(access token)和更新令牌(refresh token) 应答为json格式,示例如下: { "access_token":"0bf3ea9f460ebb770e53f2795392d88afd878000", "expires_in":3600, "token_type":"Bearer", "scope":null, "refresh_token":"f953d5fd6d4ed0fa83c20afe2fcd5a1b74374ec6" } (2)使用访问令牌(access token)从认证服务器获得对应获得授权的用户资料信息(如奥丁号,昵称和头像图片网址等) 例如: https://tool.ppkpub.org/oauth/resource.php?client_id=xxxxxxxx&state=login&access_token=上一步获得的访问令牌 应答为json格式,示例如下: { "success":true, "userInfo":{ "user_odin_uri" : "ppk:12345*" "name":"说明:可选的名称字符串", "avatar":"说明:可选的头像URL", ... //可选的更多扩展字段,由具体标识服务来自行定义 } } (3) 更新用户登录状态,然后返回应答: 参考示例源码: 动态页面(php) https://github.com/ppkpub/sdk/blob/master/php-oauth/client.php 静态页面(html+js) https://github.com/ppkpub/sdk/blob/master/php-oauth/client.html 配合静态页面的后台API(php) https://github.com/ppkpub/sdk/blob/master/php-oauth/client_api.php 注:建议使用https来提高前后端交互验证过程的信息安全 二、应用自主验证登录参考流程 采用PTTP协议自主访问一个奥丁号(例如 ppk:12345* 或 ppk:joy/12345*等),所获得PTTP应答数据报文可兼容DID协议相关定义,来支持身份标识相关认证和授权应用。 数据报文中的正文类型(content_type)取值为"text/json": 数据报文中的正文内容(content)是一个JSON编码字符串,其中包含字段"x_did",提供DID规范的描述,如该ODIN标识前缀的拥有者相关身份验证信息,具体定义如下: { ... "x_did":{ "@context": "https://www.w3.org/ns/did/v1", "id": "ppk:joy/12345*", "name":"说明:可选的名称字符串", "email":"说明:可选的联系电子邮件", "avatar":"说明:可选的头像URL", "authentication": [ //验证参数数组 { "type": "EcdsaSecp256k1VerificationKey2019", //对应bitcoin采用的secp256k1椭圆曲线算法 "publicKeyHex": "231e...." //对应公钥(HEX编码) }, { "type": "Ed25519VerificationKey2018", //对应bytom采用的Ed25519曲线算法 "publicKeyHex": "582e...." //对应公钥(HEX编码) }, { "type": "RsaVerificationKey2018", //对应RSA算法 "publicKeyPem": "-----BEGIN PUBLIC KEY...END PUBLIC KEY-----" //对应公钥(PEM格式) } ], "service": [{ //关联认证服务列表 "id":"ppk:joy/12345/vcs1*", "type": "VerifiableCredentialService", "serviceEndpoint": "https://example.com/vc/" }] } } 注: (1)上述authentication数组可以提供一个或多个公钥凭证,应用可以依序验证私钥签名直到有匹配记录。 (2)公钥类型定义参考DID规范: https://w3c-ccg.github.io/ld-cryptosuite-registry/#signature-suites-and-verification-methods (3)JSON Web Signature规范 https://tools.ietf.org/html/rfc7797 https://www.cnblogs.com/read-the-spring-and-autumn-annals-in-night/p/12041911.html (4)实际标识是否支持DID由对应标识拥有者决定,所有的ODIN根标识都默认支持(对应使用根标识注册者或管理者的公私钥)。 应用可以完全自主实现对奥丁号的登录验证,相比oAuth方案在实现上相对复杂些,但可以获得更完整的自主性。 下面只列出了用户以奥丁号登录的实现过程,给用户关联登记奥丁号的实现过程是类似的,可以参考前面oAuth验证方式中关联和登录两个功能的分别实现过程。 /* 验证时,验证发起方需要提供一段待签名的原文,JSON格式定义如下: { "uri":"所请求资源对应的ODIN标识地址字符串", "iss":"签发请求者的身份ODIN标识,可选,为空时对应开放匿名请求", "iat":到秒值的生成UNIX时间戳(issued at),可选 "exp":到秒值的过期UNIX时间戳(expires),可选 "user_agent":"可选的发出请求的用户代理属性,如PPk Javatool 0.615", } "header":{ //签名使用的标准规范定义,默认为SHA256withRSA(),可选BitcoinSignMsg(即使用椭圆曲线Secp256K算法,比特币的SignMessage方式生成签名) ,或者取值为JWT表示兼容JWT( Json Web Token, https://tools.ietf.org/html/rfc7519 )标准定义时;当请求内容涉及加密传输时可以采用JWE(JSON Web Encryption, https://tools.ietf.org/html/rfc7516),相应定义将另行定义。 "typ": "JWT", //对应签名类型为JWT时可用于进一步制定请求签名的算法(algorithm),默认是RS256(RSA+SHA256),可选ES256等,详见JWT标准定义 "alg": "RS256", }, */ 2.1 登录入口页面(Login page): 参考示例源码: https://github.com/ppkpub/OdinSwapTool/blob/master/login.php 相关实现流程如下: (A)在前端网页上放置"以奥丁号登录”的按钮 (B)在用户点击后,JS判断当前网页环境是否支持实例化PPk浏览器的PeerWeb插件, JS代码示例: if(typeof(PeerWeb) == "undefined"){ console.log("PeerWeb not valid"); //进入C1流程 }else{ console.log("PeerWeb enabled"); //进入C2流程 } (C1)如果不支持对应插件,则以AJAX形式从后端获得一个唯一登录码(与用户当前session关联),扫码验证网址和获取结果网址, 应答的JSON定义参考如下: { "code":0, "msg":"qruuid registered ok", "data":{ "qruuid":"生成的唯一登录码", "poll_url":"扫码确认网址", "confirm_url":"获取结果网址" } } 注:这里的示例是采用简单的轮训方式,可以改用WebSocket主动通知方式。 显示二维码供扫码验证, 二维码内容为上述应答里的扫码确认网址(confirm_url),并提示"等待扫码中”, 用户另行使用支持ODIN号验证的APP扫码打开访问该网址进行确认操作,应用后端将相应更新的对应登录码的状态为"已通过”。 提示"等待扫码中”的页面会通过AJAX访问获取结果网址(poll_url),定时轮询对应登录码的状态,直到状态为"已通过”, 则相应切换到用户登录后的网页,相应的网页后端处理对session的登录状态做判断处理即可。 (C2)如果支持对应插件,则读取PPk浏览器的当前缺省用户的ODIN标识,如下所示 PeerWeb.getDefaultODIN( "callback_getDefaultODIN" //回调方法名称 ); 对应JS回调方法示例: function callback_getDefaultODIN(status,obj_data){ if("OK"==status){ if(obj_data.odin_uri!=null || obj_data.odin_uri.trim().length>0){ authAsOdinOwner(obj_data.odin_uri); } }else{ alert("请先设置所要使用的奥丁号!"); } } 在回调方法里取得当前用户ODIN标识后,可进一步调用插件接口来生成签名: function authAsOdinOwner(user_odin_uri){ $.ajax({ type: "GET", url: "login_uuid.php", //在后端登记获得相应的唯一登录事务号的网址 data: {}, success: function (result) { var obj_resp = JSON.parse(result); if (obj_resp.code == 0) { //提取相应的唯一登录事务号 var qruuid=obj_resp.data.qruuid; var requester_uri="请求者的网址,可以填写为当前网址"; var auth_txt=requester_uri+","+user_odin_uri+","+qruuid; //需要签名的原文 mTempDataHex = stringToHex(auth_txt); //请求用指定资源密钥来生成签名 PeerWeb.signWithPPkResourcePrvKey( user_odin_uri, requester_uri , mTempDataHex, "callback_signWithPPkResourcePrvKey" //回调方法名称 ); }else{ alert("获取登录事务号失败!\n"+result); } } }); } 对应JS回调方法示例: function callback_signWithPPkResourcePrvKey(status,obj_data){ try{ if("OK"==status){ //提交给后端验证签名以确认登录是否通过 var auth_txt_hex = mTempDataHex ; //所签名的原文 var user_sign = obj_data.algo+":"+obj_data.sign ; //签名 var confirm_url="login_verify.php?user_odin_uri="+encodeURIComponent(user_odin_uri)+"&auth_txt_hex="+auth_txt_hex+"&user_sign="+encodeURIComponent(user_sign); $.ajax({ type: "GET", url: confirm_url, data: {}, success: function (result) { var obj_resp = JSON.parse(result); if (obj_resp.code == 0) { self.location="登录通过后的网址"; }else{ alert("用户身份标识签名验证未通过!\n"+result); } } }); }else{ alert("无法签名指定资源!\n请检查确认该资源已配置有效的验证密钥。"); } }catch(e){ alert("获得的签名信息有误!\n"+e); } } 后端验证签名时,需要调用PTTP协议SDK来获取指定用户ODIN标识属性里的公钥,然后验证所得签名是否有效并相应处理。 2.2 扫码确认登录页面(Scan qrcode to confirm) 参考示例源码: https://github.com/ppkpub/OdinSwapTool/blob/master/login_verify.php 相关实现流程如下: (A)在用户扫码打开该页面后,点击“确认登录”,JS判断当前网页环境是否支持实例化PPk浏览器的PeerWeb插件, JS代码示例: if(typeof(PeerWeb) == "undefined"){ console.log("PeerWeb not valid"); //进入B1流程 }else{ console.log("PeerWeb enabled"); //进入B2流程 } (B1)如果不支持对应插件,则将网页转向到可用的网页版PPk签名工具并附加上当前网页链接,如 http://ppk001.sinaapp.com/odin/?login_confirm_url=当前网页链接 由PPk签名工具生成签名后回调当前网页链接,并在URL里传入以下参数进行验证: qruuid : 唯一登录事务号 user_odin_uri : 用户使用的ODIN标识 auth_txt_hex :签名对应原文 user_sign : 生成的签名 response_type : 验证结果展现方式,可选html或image (B2)如果支持对应插件,则读取PPk浏览器的当前缺省用户的ODIN标识,如下所示 PeerWeb.getDefaultODIN( "callback_getDefaultODIN" //回调方法名称 ); 对应JS回调方法示例: function callback_getDefaultODIN(status,obj_data){ if("OK"==status){ if(obj_data.odin_uri!=null || obj_data.odin_uri.trim().length>0){ authAsOdinOwner(obj_data.odin_uri); } }else{ alert("请先设置所要使用的奥丁号!"); } } 在回调方法里取得当前用户ODIN标识后,可进一步调用插件接口来生成签名: function authAsOdinOwner(user_odin_uri){ var requester_uri = window.location.href; var auth_txt=requester_uri+','+user_odin_uri+','+document.getElementById("qruuid").value; //需要签名的原文 mTempDataHex = stringToHex(auth_txt); document.getElementById("auth_txt_hex").value=mTempDataHex; //请求用指定资源密钥来生成签名 PeerWeb.signWithPPkResourcePrvKey( user_odin_uri, requester_uri , mTempDataHex, 'callback_signWithPPkResourcePrvKey' //回调方法名称 ); } 对应JS回调方法示例: function callback_signWithPPkResourcePrvKey(status,obj_data){ if("OK"==status){ //提交给后端验证签名以确认登录是否通过 var auth_txt_hex = mTempDataHex ; //所签名的原文 var user_sign = obj_data.algo+":"+obj_data.sign ; //签名 var confirm_url="login_verify.php?user_odin_uri="+encodeURIComponent(user_odin_uri)+"&auth_txt_hex="+auth_txt_hex+"&user_sign="+encodeURIComponent(user_sign); self.location=confirm_url; }else{ alert("无法签名指定资源!\n请检查确认该资源已配置有效的验证密钥。"); } } 三、应用实现的参考 应用可以将奥丁号新增为类似电子邮件、手机号的用户属性之一。 数据库需要在用户表里增加一个字段,用来存关联的奥丁号,取名可以是user_odin_uri,字符串类型,长度为80个字节或以上,索引设为“不能重复”。 应用可以在用户属性编辑界面,提供“修改所关联奥丁号”的功能,并在用户输入一个奥丁号后验证是对应的合法用户再保存,验证过程可以参考上述登录过程中的验证机制。