MyInfo 使用方法

对于MyInfo 的 API 调用做一点小结

Posted by Haiming on October 22, 2019

MyInfo API Data

数据分为两种:政府确认数据和用户个人提供数据。

政府确认数据,是从政府的信息源拿到的,不可以以电子形式更改。个人提供数据是User自己在MyInfo APP 之中提供的,可以以电子形式更改。

FAQ

  1. MyInfo 不支持IP白名单,但是可以白名单下面的Fully Qualified Domain Names:
    1. test.api.myinfo.gov.sg
    2. api.myinfo.gov.sg
  2. API更新会通过邮件发送,但是并没有提及是哪个邮件……
  3. MyInfo API 只支持Web-based Integration。 暂时不支持 Native mobile applications
  4. 关于X.509 的相关问题:
    1. 在staging 和 production 环境的 key 应该不同
    2. Key 是由可信的CA 发布的
    3. 可以在不同的产品之中使用一样的public key,但是要区分staging 和 production 的环境
  5. 如果架构设计要求获得可信的MyInfo Web SSL certificate, 那么可以从下面的链接拿到:
    1. version 3 app
    2. version 2 app
  6. 在应用配置(app configurations submission)之中,对于Callback URL 的要求:
    1. 在staging 和 product 环境用不同的 callback URL
    2. 使用 static callback URL,动态URL 不支持
    3. 在staging 和 product 环境之中的callback URL之中使用Fully Qualified Domain Names (FQDN) ,不可以使用静态IP地址或者端口号。
  7. 有其他问题,support@myinfo.gov.sg

Get started

作为申请的一部分,要将APP 如何和MyInfo 互相调用的步骤用PDF画出来。模版在附件之中。

Technical Requirements

Transcation Log

下面是建议的最小追踪字段:

  • NRIC/FIN
  • Fields requested from myInfo
  • 时间戳

X.509 Public Key

密钥可以从以下被确认的CA获得:

  • digiCert
  • Entrust
  • Comodo
  • VeriSign
  • GlobalSign
  • GeoTrust

TLS 和密码套件

MyInfo 支持 TLS 1.1 和更高版本。

下面是支持的密码套件:

  • TLS_RSA_WITH_AES_256_GCM_SHA384
  • TLS_RSA_WITH_AES_128_GCM_SHA256
  • TLS_RSA_WITH_AES_256_CBC_SHA256
  • TLS_RSA_WITH_AES_256_CBC_SHA
  • TLS_RSA_WITH_AES_128_CBC_SHA256
  • TLS_RSA_WITH_AES_128_CBC_SHA

Callback URLs

除了之前提到过的,为了安全原因应该区分staging 和 production 环境等之外,Callback URLs不应该包含 # 或者 * 符号。

Toturial

Understanding OAuth2.0 flow for MyInfo APIs

全程需要使用三个不同的API。

  1. 授权 Authorise

    API 调用的是 SingPass 的验证流程,然后显示“获取用户同意”的页面,在流程结束之后,系统会返回一个存活期很短的“authorisation code”。

    API 是通过浏览器的 302 重定向触发的。

  2. 令牌 Token

    应用服务器唤醒 API 来得到 access token,这个 token 被用来调用 personal API 来获取真实数据。应用要提供和一个有效的“authorisation code” 调用 authorise API 去换取 access token。 整个 token 的存活时间是 30 分钟。

    这个API 是server-to-server call,不通过浏览器。

  3. 个人 Person

    这个 API 返回一个 JSON ,包含请求的个人数据。应用需要使用一个有效的 “access token” 来得到 JSON 数据。

    这个 API 是一个 server-to-server call。

MyInfo OAuth 2.0 Sequence Diagram

Tutorial 1:Basic Person API

官方还附带了官方教程,下面是对于官方教程之中某些需要注意的点的理解和标注。

Invoke the Sandbox Person API

在这个部分主要是有一个官方的API 进行不需要授权的访问,作用为给技术人员查看返回类型。

https://sandbox.api.myinfo.gov.sg/com/v3/person-sample/S9812381D

上面这个URL 要使用GET方法。

下面是这个URL 的返回值。

{
  "uinfin": {
    "lastupdated": "2019-03-26",
    "source": "1",
    "classification": "C",
    "value": "S9812381D"
  },
  "name": {
    "value": "TAN XIAO HUI",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "hanyupinyinname": {
    "value": "CHEN XIAO HUI",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "aliasname": {
    "value": "TRICIA TAN XIAO HUI",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "hanyupinyinaliasname": {
    "value": "TRICIA CHEN XIAO HUI",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "marriedname": {
    "value": "",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "sex": {
    "code": "F",
    "desc": "Female",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "race": {
    "code": "CN",
    "desc": "CHINESE",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "secondaryrace": {
    "code": "EU",
    "desc": "EURASIAN",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "dialect": {
    "code": "SG",
    "desc": "SWISS GERMAN",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "nationality": {
    "code": "SG",
    "desc": "SINGAPORE CITIZEN",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "dob": {
    "value": "1958-05-17",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "birthcountry": {
    "code": "SG",
    "desc": "SINGAPORE",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "residentialstatus": {
    "code": "C",
    "desc": "Citizen",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "passportnumber": {
    "value": "E35463874W",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "passportexpirydate": {
    "value": "2020-01-01",
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  "regadd": {
    "type": "SG",
    "block": {
      "value": "548"
    },
    "building": {
      "value": ""
    },
    "floor": {
      "value": "09"
    },
    "unit": {
      "value": "128"
    },
    "street": {
      "value": "BEDOK NORTH AVENUE 1"
    },
    "postal": {
      "value": "460548"
    },
    "country": {
      "code": "SG",
      "desc": "SINGAPORE"
    },
    "classification": "C",
    "source": "1",
    "lastupdated": "2019-03-26"
  },
  ...
}

Tutorial 2

在这个教程之中,官方提供了一个本机运行的程序,可以在这个程序之中的LOG看到API调用过程的交互记录。

https://github.com/ndi-trusted-data/myinfo-demo-app

首先要安装Node 和 NPM,我个人推荐使用 Homebrew 安装,方便快捷。

然后在Pull的项目文件夹之中使用 npm install,再打开命令 ./start.sh。默认程序在http://localhost:3001 之中执行。

Trigger MockPass Login and Consent(authorise API)

其大体过程为:authorise 的结尾点出发 MockPass 的登陆(实际开发之中是 SingPass 的登陆), 并且在成功登陆之后展示授权页面(consent page)。

点击“Retrive MyInfo”按钮,应用会使用下面的 URL:

https://sandbox.api.myinfo.gov.sg/com/v3/authorise

在URL 之中有下面的参数:

PARAMETER DESCRIPTION
client_id 每个应用不同的ID,在示例之中是 STG2-MYINFO-SELF-TEST
attributes 用逗号分隔,这个是必选项,可用的 attribute 在 Person 的定义之中都有
purpose 要数据的作用。这个 purpose 会展示在用户的界面,告知为何使用此数据
state identifier to reconcile query and response. This will be sent back to you via the callback URL. Use a unique system generated number for each and every call.
redirect_uri 给MyInfo 返回 authorisation code 的URL,在此示例程序之中为 http://localhost:3001/callback

在授权页面点击 “I Agree” 之后,会进入获得Authorisation’ Code的环节,在 console 的 URL dialog 之中可以看到类似于下方的信息:

http://localhost:3001/callback?code=e2369168-52da-421a-b70f-03f64e779c4b&scope=edulevel%20regadd%20mobileno%20hanyupinyinname%20marriedname%20assessableincome%20cpfcontributions%20email%20housingtype%20race%20sex%20hdbtype%20cpfbalances%20hanyupinyinaliasname%20marital%20aliasname%20nationality%20dob%20name&iss=http%3A%2F%2Fstg-auth.app.gov.sg%3A80%2Fconsent%2Foauth2&state=123&client_id=myinfo

这个URL之中以下信息是非常重要的:

code 之后的一串标志符,API之中叫做 authorisation ,这个案例之中是 e2369168-52da-421a-b70f-03f64e779c4b, 这个标识符在后面要用来唤醒另一个API。

在授权页面(有是否同意的那个页面) 点击同意之后, token 和 person 的 API 会自动在后端唤醒,下面是这两个API 的内容。

Call the Token API

如果已经有了authorisation code, 那么就可以使用 token API 来获得 access token了。

注意:

这个API 必须通过服务器端调用,不可以从浏览器端和移动端调用,因为无法存储私钥。

所以下面的信息不在浏览器的调试端口之中出现,而是在后端打开的Terminal 之中出现。

如果想看具体的代码,可以看 routes/index.js,其功能为生成TOKEN 的 POST 请求。

示例程序调用的是 https://sandbox.api.myinfo.gov.sg/com/v3/token ,注意其方法为POST

POST 的body 之中可选的参数为:

PARAMETER DESCRIPTION
grant_type TOKEN的类型 (默认为 “authorization_code”)
code authorise API 提供的authcode
redirect_uri 提供给 MyInfo 使用的 callback URL, 示例程序之中的是 http://localhost:3001/callback
client_id 之前提到过的每个应用不同的ID,示例程序之中为 STG2-MYINFO-SELF-TEST
client_secret 授予程序的密钥,在示例程序之中为 44d953c796cccebcec9bdc826852857ab412fbe2

在发完请求之后,可以在Terminal 之中的Log 看到Token API 的回应消息。

Response from Token API:{"access_token":"eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoiRWtnWkZDeG5taXY2T2JDZ3B4blRIRUkyK3FVPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJTOTgxMjM4MUQiLCJjdHMiOiJPQVVUSDJfU1RBVEVMRVNTX0dSQU5UIiwiYXV0aF9sZXZlbCI6MCwiYXVkaXRUcmFja2luZ0lkIjoiYzNjOTU1MjUtNzEwYS00ZjU3LWFhZTMtMzEzMjUwZDkxOWE3LTEzOTc3NiIsImlzcyI6Imh0dHBzOi8vY29uc2VudC5jbG91ZC5teWluZm8uZ292LnNnL2NvbnNlbnQvb2F1dGgyL3JlYWxtcy9yb290L3JlYWxtcy9teWluZm8tY29tIiwidG9rZW5OYW1lIjoiYWNjZXNzX3Rva2VuIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsImF1dGhHcmFudElkIjoiZDVzZ3RWZHl1UFNOc0haTHJkYVUyMTAwTV9zIiwiYXVkIjoibXlpbmZvIiwibmJmIjoxNTUzNTk0OTc4LCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwic2NvcGUiOlsiZWR1bGV2ZWwiLCJtb2JpbGVubyIsImFzc2Vzc2FibGVpbmNvbWUiLCJvd25lcnByaXZhdGUiLCJuYXRpb25hbGl0eSIsImRvYiIsImNwZmNvbnRyaWJ1dGlvbnMiLCJlbWFpbCIsInNleCIsImhvdXNpbmd0eXBlIiwiY3BmYmFsYW5jZXMiLCJuYW1lIiwicmVnYWRkIiwicmFjZSIsImhkYnR5cGUiLCJtYXJpdGFsIiwiYXNzZXNzeWVhciJdLCJhdXRoX3RpbWUiOjE1NTM1OTQ3NzIsInJlYWxtIjoiL215aW5mby1jb20iLCJleHAiOjE1NTM1OTY3NzgsImlhdCI6MTU1MzU5NDk3OCwiZXhwaXJlc19pbiI6MTgwMCwianRpIjoieGRIN1QwSjI0TmFHS1FpWWVnNjcyREJfZGdrIn0.jbdjui3WLe-cwPRDCCR09ya5fK4UUntx31Y87PosGV_FTnKTmiy_cYOeaVTpjLmPx4ebo0fLooPHpKH_5_4lFPVaNdQkOGjvScV1fl04DR1UW0uutQubkIalYW-WgmIDhQz4ZddXyLswUnGc7-eURR47VDzjiMr-ptcn0uSfrI1RNgnc8kY12slOAE4bGxxmYE_PlBLQuZiCdORD9JKKjEKAptKVyQF7p9o6EAg2TQe4cpwcDLXYUkwjLcaoEdCXmX16QICFm9RsVFaW_PRl29fY9ErxcN27UrRnj4mqfbYUuRnN-W2e6DSnMfkZwMRKOlmPgD7fflfh5dnuNwGAXQ","scope":"edulevel mobileno assessableincome ownerprivate nationality dob cpfcontributions email sex housingtype cpfbalances name regadd race hdbtype marital assessyear","token_type":"Bearer","expires_in":1799}

可以看到其主要分为三个方面:access token, token type 和 expire time。

JWT的组成

JWT由三部分组成,Header,Payload,Signature。所以一个典型的 JWT 是 xxxxx.yyyyy.zzzzz

Header 部分和 Payload 部分是 Base64Url 编码的,signature 是一个加密和和一个 Hashing 算法一起使用产生的。

Call the Person API(With the access token)

有了token 就可以访问Person API 来得到用户信息了,API 的注意事项和之前一样,不可以被浏览器或者native app 调用,只可以被服务器端使用。

API的注意事项和之前一样,也是要有有效的 TOKEN, 不然直接拒绝。

这一步的操作逻辑在 index.js 之中,其逻辑主要是为了发送请求做准备,所以要对请求头和请求内容做生成。在请求之中的参数有:

PARAMETER DESCRIPTION
client_id 应用程序的独特的ID,示例程序为 STG2-MYINFO-SELF-TEST
attributes comma separated list of attributes requested. Possible attributes are listed in the Person object definition in the API specifications.

在请求头之中提供Access Token

Access Token 应该在API 的header之中提供。

在Terminal之中可以看到类似于下面的内容:

headers:{"Cache-Control":"no-cache","Authorization":"Bearer eyJ0eXAiOiJKV1QiLCJ6aXAiOiJOT05FIiwia2lkIjoiRWtnWkZDeG5taXY2T2JDZ3B4blRIRUkyK3FVPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJTOTgxMjM4MUQiLCJjdHMiOiJPQVVUSDJfU1RBVEVMRVNTX0dSQU5UIiwiYXV0aF9sZXZlbCI6MCwiYXVkaXRUcmFja2luZ0lkIjoiYzNjOTU1MjUtNzEwYS00ZjU3LWFhZTMtMzEzMjUwZDkxOWE3LTEzOTc3NiIsImlzcyI6Imh0dHBzOi8vY29uc2VudC5jbG91ZC5teWluZm8uZ292LnNnL2NvbnNlbnQvb2F1dGgyL3JlYWxtcy9yb290L3JlYWxtcy9teWluZm8tY29tIiwidG9rZW5OYW1lIjoiYWNjZXNzX3Rva2VuIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsImF1dGhHcmFudElkIjoiZDVzZ3RWZHl1UFNOc0haTHJkYVUyMTAwTV9zIiwiYXVkIjoibXlpbmZvIiwibmJmIjoxNTUzNTk0OTc4LCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwic2NvcGUiOlsiZWR1bGV2ZWwiLCJtb2JpbGVubyIsImFzc2Vzc2FibGVpbmNvbWUiLCJvd25lcnByaXZhdGUiLCJuYXRpb25hbGl0eSIsImRvYiIsImNwZmNvbnRyaWJ1dGlvbnMiLCJlbWFpbCIsInNleCIsImhvdXNpbmd0eXBlIiwiY3BmYmFsYW5jZXMiLCJuYW1lIiwicmVnYWRkIiwicmFjZSIsImhkYnR5cGUiLCJtYXJpdGFsIiwiYXNzZXNzeWVhciJdLCJhdXRoX3RpbWUiOjE1NTM1OTQ3NzIsInJlYWxtIjoiL215aW5mby1jb20iLCJleHAiOjE1NTM1OTY3NzgsImlhdCI6MTU1MzU5NDk3OCwiZXhwaXJlc19pbiI6MTgwMCwianRpIjoieGRIN1QwSjI0TmFHS1FpWWVnNjcyREJfZGdrIn0.jbdjui3WLe-cwPRDCCR09ya5fK4UUntx31Y87PosGV_FTnKTmiy_cYOeaVTpjLmPx4ebo0fLooPHpKH_5_4lFPVaNdQkOGjvScV1fl04DR1UW0uutQubkIalYW-WgmIDhQz4ZddXyLswUnGc7-eURR47VDzjiMr-ptcn0uSfrI1RNgnc8kY12slOAE4bGxxmYE_PlBLQuZiCdORD9JKKjEKAptKVyQF7p9o6EAg2TQe4cpwcDLXYUkwjLcaoEdCXmX16QICFm9RsVFaW_PRl29fY9ErxcN27UrRnj4mqfbYUuRnN-W2e6DSnMfkZwMRKOlmPgD7fflfh5dnuNwGAXQ"}

做完之后,可以在Terminal 之中看到所请求的客户信息内容:

Person Data (Decoded):
{"name":{"lastupdated":"2019-03-26","source":"1","classification":"C","value":"TAN XIAO HUI"},"sex":{"lastupdated":"2019-03-26","code":"F","source":"1","classification":"C","desc":"FEMALE"},"race":{"lastupdated":"2019-03-26","code":"CN","source":"1","classification":"C","desc":"CHINESE"},"nationality":{"lastupdated":"2019-03-26","code":"SG","source":"1","classification":"C","desc":"SINGAPORE CITIZEN"},"dob":{"lastupdated":"2019-03-26","source":"1","classification":"C","value":"1998-06-06"},"email":{"lastupdated":"2019-03-26","source":"2","classification":"C","value":"myinfotesting@gmail.com"},"mobileno":{"lastupdated":"2019-03-26","source":"2","classification":"C","areacode":{"value":"65"},"prefix":{"value":"+"},"nbr":{"value":"97399245"}},"regadd":{"country":{"code":"SG","desc":"SINGAPORE"},"unit":{"value":"128"},"street":{"value":"BEDOK NORTH AVENUE 4"},"lastupdated":"2019-03-26","block":{"value":"102"},"source":"1","postal":{"value":"460102"},"classification":"C","floor":{"value":"09"},"type":"SG","building":{"value":"PEARL GARDEN"}},"housingtype":{"lastupdated":"2019-03-26","code":"","source":"1","classification":"C","desc":""},"hdbtype":{"lastupdated":"2019-03-26","code":"113","source":"1","classification":"C","desc":"3-ROOM FLAT (HDB)"},"marital":{"lastupdated":"2019-03-26","code":"2","source":"1","classification":"C","desc":"MARRIED"},"edulevel":{"lastupdated":"2019-03-26","code":"3","source":"2","classification":"C","desc":"SECONDARY"},"ownerprivate":{"lastupdated":"2019-03-26","source":"1","classification":"C","value":false},"cpfcontributions":{"lastupdated":"2019-03-26","source":"1","history":[{"date":{"value":"2018-02-18"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2018-01"}},{"date":{"value":"2018-03-18"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2018-02"}},{"date":{"value":"2018-04-18"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2018-03"}},{"date":{"value":"2018-05-18"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2018-04"}},{"date":{"value":"2018-05-27"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2018-05"}},{"date":{"value":"2018-07-15"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-01"}},{"date":{"value":"2017-02-01"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-01"}},{"date":{"value":"2017-02-12"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-02"}},{"date":{"value":"2017-02-21"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-02"}},{"date":{"value":"2017-03-01"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-02"}},{"date":{"value":"2017-03-12"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-03"}},{"date":{"value":"2017-03-21"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-03"}},{"date":{"value":"2017-04-01"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-03"}},{"date":{"value":"2017-04-12"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-04"}},{"date":{"value":"2017-04-21"},"employer":{"value":"Crystal Horse Invest Pte Ltd"},"amount":{"value":500},"month":{"value":"2017-04"}}],"classification":"C"},"cpfbalances":{"oa":{"value":1581.48},"ma":{"value":11470.7},"lastupdated":"2019-03-26","source":"1","classification":"C","sa":{"value":21967.09},"ra":{"value":0}},"uinfin": {"lastupdated": "2019-03-26","source": "1","classification": "C","value": "S9812381D"
}}

Tutorial 3:Implementing PKI Digital Signature

首先要生成Public/Private Key Pair,这一步不多说。

给请求添加签名

签名请求需要的步骤如下:

  • 创建一个 Authorisation Token
  • 生成 Signature Base String
  • 使用数字签名来得到签名之后的 Base String
  • 组装 Header

下面是每一步之中需要注意的点:

  1. 创建一个 Authorisation Token

    NAME DESCRIPTION
    app_id 应用的独特ID,示例STG2-MYINFO-SELF-TEST`
    nonce 一个随机的String,对于每个请求都是独立生成的。用来分辨请求。
    signature_method 签名方式. Value = RS256
    signature 签名值
    timestamp 从1970年1月1日 GMT 的毫秒数
  2. 生成Signature Base String

    Base String 是用来代表请求内容的String,可以被用来确认内容在传输的过程之中没有被修改。

    下面是在Person API 之中的 Base String 的内容:

    baseString:
    GET&https://test.api.myinfo.gov.sg/com/v3/person/S9812381D/&app_id=STG2-MYINFO-SELF-TEST&attributes=name,sex,race,nationality,dob,email,mobileno,regadd,housingtype,hdbtype,marital,edulevel,assessableincome,hanyupinyinname,aliasname,hanyupinyinaliasname,marriedname,cpfcontributions,cpfbalances&client_id=STG2-MYINFO-SELF-TEST&nonce=150589435395700&signature_method=RS256&timestamp=1505894353957
    

    Signature Base String 的生成方式如下:

    首先按照字典排序来将parameter 进行排序,如果几个parameter 分享一个key, 那么再对其使用 value 进行排序,例子如下:

    •   a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t
      

    所有的parameter之间使用&来进行链接,例子如下:

    •   a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t
      

建立请求URL

Signature Base String 包括请求的 绝对URL,在 Signature base String 之中使用的URL 必须:

  • 包括 Scheme,authority 和 path
  • 不包含 query 和 fragment

URL scheme 和 authority 必须是小写字母, 并且必须包括除了80和443之外的接口。

HTTP 请求必须用 & 连接在一起,哪怕是空的请求也一定要这样。

对Base String 做签名来获得数字签名

当Signature Base String 创建之后,下一步就是签名

  1. 用SHA-2 算法来产生Signature Base String 的 hash
  2. 用应用的私钥来签名hashed value
  3. 用 Base64-encode 来对signature value 签名

注意: Base64 编码不应该包含 CRLF,整个 Base64 编码都不应该有换行。

  1. 将这个字符串作为Signature参数的值

下面是示例的Java 代码:

String baseString = "Constructed base string";
Signature sig = Signature.getInstance("RSA-SHA256");
sig.initSign(privateKey); // Get private key from keystore
sig.update(baseString.getBytes());
byte[] signedData = sig.sign();
String finalStr = Base64.getEncoder().encodeToString(signedData);

Assembling the header 组装头部

已经有了数字签名之后,就可以将最终的header 装配好。下面的部分是在header 之中需要包括的:

  • App_id
  • nonce
  • Signature_method
  • signature
  • timestamp

这些参数在之前都有讲过,就不再赘述了。

下面是带有authorization parameter 的示例头部:

Authorization: PKI_SIGN
timestamp="1505900210349",
nonce="150590021034800",
app_id="STG2-MYINFO-SELF-TEST",
signature_method="RS256",
signature="EEm+HEcNQajb5FkVd82zjojk+daYZXxSGPCOR2GHZeoyjZY1PK+aFMzHfWu7eJZYMa5WaEwWxdOdq5hjNbl8kHD7bMaOks7FgEPdjE++TNomfv7SMktDnIvZmPYAxhjb/C9POU2KT6tSlZT/Si/qMgD1cryaPwSeMoM59UZa1GzYmqlkveba7rma58uGwb3wZFH0n57UnouR6LYXDOOLkqi8uMZBuvRUvSJRXETAj2N0hT+4QJiN96Ct6IEQh/woZh0o74K5Ol9PpDSM08qC7Lj6N/k694J+hbBQVVviGn7/6mDkfbwdMDuoKs4t7NpqmAnwT+xaQSIZcexfrAVQYA=="

在确认好之后,将请求发送出去,要确认URL 的域,例如 https://api.myinfo.gov.sg/com/v3/*

在接受请求的回应之后,要核实请求的 JWT 是否是有效的。