[toc]
## **请求Token**
在请求Token的API中,有以下参数
#### **Query Parameters**
* `service`:(neccessary)授权服务的标识,表示要向谁请求token
* `scope`:(neccessary)
* `client-id`:(optinal)请求token的客户端id,比如docker-daemon发起的请求会将该字段设置为`docker`,harbor的复制策略中会将该字段设置为`harbor-registry-client`
#### **Header Parameters**
* `Authorization`:(optional)携带的用户信息
#### **Response Body**
响应body为一个json,有三个字段
* `token`:(neccessary)授权服务器返回的带有授权信息的token
* `issued_at`:(optional)token的签发时间,UTC标准时间格式
* `expires_in`:(optional)token在多少秒以后过期,如果没有说明则默认为60秒
#### **示例**
```
$ curl 192.168.1.103:8021/service/token?service=token-service\&scope=repository:library/registry:pull\&client_id=curl
{
"expires_in": 1800,
"issued_at": "2018-09-05T08:34:40Z",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkhNNjY6NkNYUzpaQlBROk1ENVo6QlJZVTpTVE9EOkNCUEs6Uk5ORjpYN0VDOkZMUUw6TFNFMjpLUUtTIn0.eyJpc3MiOiJyZWdpc3RyeS10b2tlbi1pc3N1ZXIiLCJzdWIiOiIiLCJhdWQiOiJ0b2tlbi1zZXJ2aWNlIiwiZXhwIjoxNTM2MTM4MjgwLCJuYmYiOjE1MzYxMzY0ODAsImlhdCI6MTUzNjEzNjQ4MCwianRpIjoiZFpxVkgxVDFjZkhXdnFZTiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoibGlicmFyeS9yZWdpc3RyeSIsImFjdGlvbnMiOlsicHVsbCJdfV19.ilCKa2-oJ9bKQpAo8ntcx1lHpbs0BcWYtbRrvItHAProaDEDpll9EZrzkzg6XR9OOLByFm_oJKKk8Y_wYwQfxYdjvhLbFjCNXzE6MckY8dEcSR5BmYxOK54zAqNVkw24ugUcagGFi7p8Gy0YZqBqf7AP8qCarhuWhKsZ7B4esMQk2xBEn1hh8r_9tb6wnZOkDl7trW0IWbPkqKSaP8ycq8oS9J0T6zaItyTLnERsV_GFJOh6DdfhSYzGwoWUFQH6cmp05ZHXF_-4O6N6d8tosGH9gTsam-ffeVHmWp8da_gpS_R15z3ELR5I2FO0s4gWo1UbTI3yuyV8stSURrCs6GHZSMb2C9_2R2r_Q-uDKmdpoazw2G1DxM3PgfXEwANWEjPJMjD0areUXmjwz_hefSMqYFxLi26TaQinG0th7pNz5m0qroefOy1AGyhRZK-t8rsduZJ9EWQCqtXHrPbTES0FoItJmcMqcJZcvQsrJsBMirtijvGdNn55l44-eDFyrIuExerHzU1dJoSijCtqIYxbdnclLE8HSP-vnBD5TOAJoUUdUfA1N8TvF2QqDjr_LATUOctahrFoWiuDjrFXH-ptmcJJ6lPjo1oCOne3ImKe_mieRR7YCOQLejuCbItIIweuqwBzJU5d33k3Drra0qvbvk-MkO7iBNgpCtfWqD8"
}
```
我们可以把上面的token拷贝到网页 jwt.io 中,查看token的明文形式。
在上面获取token的请求中,没有携带任何的用户信息。不过我们可以使用添加用户信息(用户名与密码)去获取token,如下:
```
$ curl -H "Authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" 192.168.1.103:8021/service/token?service=token-service\&scope=repository:library/registry:pull\&client_id=curl
```
其中`YWRtaW46SGFyYm9yMTIzNDU=`是`admin:Harbor12345`的base64编码
## **生成Token**
token由三部分内容组成:Header、Payload和Signature。token的形式如下:
```
{token-header}.{token-payload}.{token-signature}
```
#### **Header**
Header有三个字段
* `typ`:固定为`JWT`
* `alg`:签名算法,常用的有`HS256`、`RS256`等
* `kid`:key-id,签名算法中所使用的密钥的ID值
`kid`的生成有以下三个步骤
1、从签名算法使用的密钥中得到`DER`编码格式的公钥(public key) 2、对`DER`格式的公钥做sha256哈希,取前240bit 3、将这240bit使用base32编码,然后四个一组使用冒号`:`分隔
如下是Header的一个例子
```
{
"typ": "JWT",
"alg": "RS256",
"kid":"HM66:6CXS:ZBPQ:MD5Z:BRYU:STOD:CBPK:RNNF:X7EC:FLQL:LSE2:KQKS"
}
```
生成`kid`的详细例子见本文末尾的扩展阅读
#### **Payload**
payload中的字段有
* `iss`:(Issuer),token的签发者
* `sub`:(Subject),正在进行认证的用户的名字,如果是匿名用户则为空
* `aud`:(Audience),token的观众,即需要对token进行验证的服务的名字
* `exp`:(Expiration),过期时间,在这之后token应该看作是无效的;时间戳格式
* `nbf`:(Not Before),token有效的超始时间,在这之前token应当看作是无效的;时间戳格式
* `iat`:(Issued At),签发时间;时间戳格式
* `jti`:(JWT ID),token的id,(尚不清楚如何生成)
* `access`:权限集,下面还有三个字段
* `type`
* `name`
* `actions`
payload的样例如下:
```
{
"iss": "registry-token-issuer",
"sub": "",
"aud": "token-service",
"exp": 1536204479,
"nbf": 1536202679,
"iat": 1536202679,
"jti": "KF76FTQQ4tIvvCbR",
"access": [
{
"type": "repository",
"name": "library/registry",
"actions": [
"pull"
]
}
]
}
```
#### **Signature**
###### **Header**
对header内容去掉空白字符后得到
```
{"typ":"JWT","alg":"RS256","kid":"HM66:6CXS:ZBPQ:MD5Z:BRYU:STOD:CBPK:RNNF:X7EC:FLQL:LSE2:KQKS"}
```
然后对该字符串进行base64Url编码([base64在线编码网址](https://base64encode.net/)),得到token-header
base64Url就是先进行base64编码,再把得到的字符串中的`+`变成`-`,`/`变成`_`,去掉`=`
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkhNNjY6NkNYUzpaQlBROk1ENVo6QlJZVTpTVE9EOkNCUEs6Uk5ORjpYN0VDOkZMUUw6TFNFMjpLUUtTIn0
```
###### **Payload**
payload内容去掉空白字符后得到
```
{"iss":"registry-token-issuer","sub":"","aud":"token-service","exp":1536204479,"nbf":1536202679,"iat":1536202679,"jti":"KF76FTQQ4tIvvCbR","access":[{"type":"repository","name":"library/registry","actions":["pull"]}]}
```
然后对该字符串进行base64Url编码,得到token-payload
```
eyJpc3MiOiJyZWdpc3RyeS10b2tlbi1pc3N1ZXIiLCJzdWIiOiIiLCJhdWQiOiJ0b2tlbi1zZXJ2aWNlIiwiZXhwIjoxNTM2MjA0NDc5LCJuYmYiOjE1MzYyMDI2NzksImlhdCI6MTUzNjIwMjY3OSwianRpIjoiS0Y3NkZUUVE0dEl2dkNiUiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoibGlicmFyeS9yZWdpc3RyeSIsImFjdGlvbnMiOlsicHVsbCJdfV19
```
###### **signature**
token-signature的计算方法如下,先对`token-header + "." + token-payload`做sha256哈希(RS256就是RSA+SHA256),然后再使用RSA的私钥进行签名(sign),最后用base64Url进行编码,得到signature-token
```
token-signature = base64Url( sign( sha256( token-header + "." + token-payload ) ) )
```
由前面的token-header与token-payload得到的token-signature如下(RSA密钥见扩展阅读)
```
e91bTpXSYNUTcUXr7zs62ZgCm1L6bhZbbW4ujXFY9Zzdkvy3DEHDssq6R4K9f5ESvv_LrWxxIxIXVREAATw-FaykcAewyjarC6Vlj2g0ea6D9L1HsIvsqtYcBOnHIJ5CRPJPhXWPwBtbujgNgbti-LLeVprOwaJ8fDk21UikmYFhX61_IobFukWw1ByXiNt8byU6tOrxkkDp-YXpz9y-XP5FdheGwNxOREph40znA9LddUcEuQUHB5WKQ3tdU4sqXOW3TUCtjLOl-kVREcus-83fLSuob1lZWRbzU9dEROd_5ZP4NNmD4ZY0DhcYbp75UqvB-MZIiC9MDeOheHAsPGB4Kqu2gBshRd_NJIrQkig7yvD2Wo7twn1KKSznHp6lcsK5phkkkWMVbZoD3qV76MqCDKVSkD2JOgQ0l4AhcYEGLtxx_ukk4NlDCYoljnGPw1oEynmFDROSvMg_bqhRVUF-5US83sU0l6YWwRCZT6StTvdSHp79wbSXgEn58-NO64AtVuMEb1XiDhDxtgaF0K61UwjBRmhpcCurw0laknBVVlta6otbfQcbyQn6ulsKgbrBKka-vkgo4_ymCyqnSXuZYC2Oz_PYawgGVz3s4JXhedoVWiUSDbyKYnFTXdtTign5oT6H6N-K1YKLoGSxma3uUwdDZP2hKH_UH_V9eOY
```
最后,对token-header、token-payload和token-signature进行组装,得到最终的token
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkhNNjY6NkNYUzpaQlBROk1ENVo6QlJZVTpTVE9EOkNCUEs6Uk5ORjpYN0VDOkZMUUw6TFNFMjpLUUtTIn0.eyJpc3MiOiJyZWdpc3RyeS10b2tlbi1pc3N1ZXIiLCJzdWIiOiIiLCJhdWQiOiJ0b2tlbi1zZXJ2aWNlIiwiZXhwIjoxNTM2NTY4NDcxLCJuYmYiOjE1MzY1NjY2NzEsImlhdCI6MTUzNjU2NjY3MSwianRpIjoiaU1kYm5td2dLQ2dUTk4xdyIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoibGlicmFyeS9yZWdpc3RyeSIsImFjdGlvbnMiOlsicHVsbCJdfV19.e91bTpXSYNUTcUXr7zs62ZgCm1L6bhZbbW4ujXFY9Zzdkvy3DEHDssq6R4K9f5ESvv_LrWxxIxIXVREAATw-FaykcAewyjarC6Vlj2g0ea6D9L1HsIvsqtYcBOnHIJ5CRPJPhXWPwBtbujgNgbti-LLeVprOwaJ8fDk21UikmYFhX61_IobFukWw1ByXiNt8byU6tOrxkkDp-YXpz9y-XP5FdheGwNxOREph40znA9LddUcEuQUHB5WKQ3tdU4sqXOW3TUCtjLOl-kVREcus-83fLSuob1lZWRbzU9dEROd_5ZP4NNmD4ZY0DhcYbp75UqvB-MZIiC9MDeOheHAsPGB4Kqu2gBshRd_NJIrQkig7yvD2Wo7twn1KKSznHp6lcsK5phkkkWMVbZoD3qV76MqCDKVSkD2JOgQ0l4AhcYEGLtxx_ukk4NlDCYoljnGPw1oEynmFDROSvMg_bqhRVUF-5US83sU0l6YWwRCZT6StTvdSHp79wbSXgEn58-NO64AtVuMEb1XiDhDxtgaF0K61UwjBRmhpcCurw0laknBVVlta6otbfQcbyQn6ulsKgbrBKka-vkgo4_ymCyqnSXuZYC2Oz_PYawgGVz3s4JXhedoVWiUSDbyKYnFTXdtTign5oT6H6N-K1YKLoGSxma3uUwdDZP2hKH_UH_V9eOY
```
## **使用Token**
在得到token后,我们就可以在API请求的Header中添加token信息,比如下载镜像的manifest
```
$ curl -H "Authorization: Bearer [token]" 192.168.1.103:8021/v2/library/registry/manifests/2.5.0
```
## **验证Token**
当Registry接收到一个携带token的API请求时,Registry需要从以下几个方面来验证Token
* token的签发者(payload中的`iss`)是可信的,即和registry的配置参数`issuer`一致
* 确保registry是该token的观众,即payload中的`aud`与registry的配置参数`token-service`一致
* 检查payload中的`nbf`与`exp`确保token在有效期内
* 检查payload的access字段,确保该token能够访问该API
* 检查token的签名
## **扩展阅读**
本文中使用到的RSA的私钥为(`private_key.pem`)
```
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEAzo+QgcandPCMwXgRZ6zA77ko5+i+f+4yuEll/VJtd2RVmAXD
QxAc1pHuCdC+XyW0Hzr0CxrQb7IY3rhZQqY4Jp08Ros7vleurSion36tTp0c8Ifc
S6Yqzzfk96dilmp5z5IE+TlfnQN2HLOtj9350DAB3rJyHPjidH8/2+8EOvlDwh3a
obdJopzQrPbe4Jv4jgBjQbey/dKpA+A5UYoV6RuYA/luhz5l6lSgqc3x7dnlZ/ou
nReUu6K58G3GoJ3KxM2ekH70XQJkGh9KBiJgb15E4uSVYEx2tH3KZCmW3O7N4QUD
CLK7ZILF41tjk1p4wFVByOlaSteRLW3EET2nEAARlIoXkYV2P0byk2h8Knu/Z1pf
iravEe9M0oT3hT/ZBgndMN5bnxPbOER+OHailsj1dLJSjdzud2l4hScJF/lNNHI4
lI6KwfaLw61yuFkwIDQdJMwYW+wt5VtZf1RGhvnTDEj2gsMmB2a+nNKeLgyM3lL8
2ZfOu2SZPCr4QxfTnzfhahkukdGGsL1CiD7jQU2NAAvNZJUqqNHl2w8weukIBrkW
bt56T1lOVYwaIUGW06M3Iq7MDO3PfUEC1hWDVAmTAm93vCMiu97Burgp/M1QBfWw
LQ8Tah+fzRTt8NwiUXMExaZYdLfZIThxzNlWlqKHFdZc9YqLFdjaPSthteMCAwEA
AQKCAgAOFEUCQ3sYgmjlqvxst56y+EjsfbW2XJMCcqZL/PlPIPygjwv/HzMIAQxb
iOng7F35nvgRZbN9WYNOcvxKia/cGe2I1WauE6XpUZMkw+qmKBlX37rJQTs7wpCN
vNAAdqN03XwPTLTSq/C6Bhk3bCbh5NPLzRfwF5q/3AiLQiBksKbIrWZAjZCsT8n9
cBpC7v6jFy2sxguiN2Cjzf26LBJQQDw9URwShdNGhJwq1sm9r5NuYeQZewj9PRs1
YxYdzoOKpIVBThXz3PzbtvRBtMgj7yX83R29YZjZtpU7/IW262QHCWNqjVwufqdk
Vs9TtN/0JBuGyTkJTuYrVYb+sdgYJL9WI/k2aWDhli29Ai/yI+Zm+NPAoVIZHP25
OsdjJctV08uYwTkjTg9qwpv/Jge9lKjJbBcB8ldDL6jRL89+PiYS4NvED0Jc1Xs5
jgScXAB8fD2TGbVbb1x9lLs0iGUe6WlRv3zwIEQiVJkPrjXNIpl8bO3iL3AoQLoZ
I2oaIuIGxQTSgu/+e00JEwDis1/kGSGjf1iHz9Q2o8tTJzphR6NFZKexclO3QQJd
44ELlMo43ZsRy+khbx/PYZbcRHALD8VjR2p1aERiYbGpZBGvJbPvdefXQgd3K8nF
KfvqnoAsOIW8TUaSyhE+2zZnfjLyyuyFmTREtzOAhouw12MmWQKCAQEA6NnNDMh5
Pa68fTI1MLi0eawjZx7SwcGqcynGLh2nH2PbA6IZc5Th1I4VXYu43dqU8LlylFvq
6fQEkxvpuVYRorgCxoJqPqjm42i062jbWMHZ7ZKX04HRqgO6f2Tf+RFpdVdXBeL2
ybt3L6ZWkB5z77P7Pcs4xsOAbcy5pEIINDBsIchwmHTge05Jcz6ZIPIpZe9n7wDA
chA9ipgu6uRnpk2w5P6des6QFPN8uoHzZ3Ap81n/0lYOKgzi0dp5grLzyOKjTrlN
mzQ2/FtnwGhMeUUNqhNko33+b2Lijrh6ezK+erxD68UKKn4Sj/cbvKsDpRz8U/Fs
D0fnzVetBFaLPwKCAQEA4xiqs9jYD2167u0sTknjtOfycYVKm0+4zS5wqKc16fx/
JY38ffslmIwvy5FCN5dfwF9rsAUTiqnmJdlvb7k2CjqWMeSIeDQleJIe570MIcsl
DUky3WW0Vuo2taDDmTxgQI8DGVDjwp0iFRaKCnrzV3BajG++p/2qb998prvfQLLA
EHKEuHVMg1mtuEfOF4vqZmYGjFJG7oyb/anFcNTmgE27jnUchT6njojfPqhV3Qca
8JHPE4oIm3U0+V+dqNw12sR79zWzxrJ6rcmUJMwbTWPng0y2bsBkG+zbo1E5CLEQ
1paDu+auxUmVFENKb8ztt7Vfupc2EdvAo5ia3WbgXQKCAQEA0J+DxjY/2nIaUxmO
6o4ytOjz90p4jjzUWMZO17adq9QtwH2VzCbShzyeC+hJxAw5cczVyfLo8KA/EQbr
S7C/sEipw+3I/0cZRxrjLiAOluFoPiEfgtNHZMpeaBGbUm61S/rq701Ay9H4oWqp
GAsQ2O0q51yTDBLRmI7arT60Vv4jg8kwiIf/MLsdt/GYBRqy2K+9MTg9NHU0jl53
euEVtLzbBvDRa9xy3zKgyAHycPTfwTcbq/qKSkatWlQilmV7YrsckkYYMDyCH2xN
8uf/zI+ABKfHfWw/cNDqJ/FFW+hFHXZcbHtn9lZqjy5ZXZrjcyYbNaKSrMZB+4rY
a5CWxQKCAQEAn/V196wbs/I3jye794EQRRLDsLZkcLVcxBmb/Q+aaDAUFw3F9a77
MlI8MDUm4SVcqpILtjY9J4S4uZxIY/efWuEdfhMtFQ4V/rFd13lPnFYMySjwDQZg
WoAq/RA59iuS2KZjVmelpiUsJpJztSIZWVOoVBc5wfZpINfYY1Ed1eKSaoNffNYS
iMqYFJ9vSSKifnIK1rf1gn3EOo5kpi8wFNur6pIO/sO9HibGqMnFgSRKE32A0JB/
s5CBOc3hrVk/DdMsRlqrQJ/izZqZILor2P0vy0ozjhsx6IGTy5uggsDFzYDDVY0N
OaW0vksPmWRNZQL6ZOGxki6pqBILszuNeQKCAQEA179mOscNdSlHog24Sjshjxrp
64vSLBmEzdu/eF/2D5ZP++lOxgiqqWhhv8j06gHkuIKCEbA4mEN2przdtNDrYq9a
ohEJdnuU5utbsk57TosgKGT8zXC14CXXSxU40pB/lZUC1bebAK7JWQrDBFWKMshT
UJrrfqzdqg3Cw3YES/fkbzN+GAZY6fiFDOrgzIaIMTJoyivtTi97OY10aGoSYCHN
n3ptiquNC154FFkNID3RgrMe4C2du4tzfw7mIcPiR1V07MikkVpvsBFBGuSqUDl9
OudlMkXCFVkmGFaCFFzrxtdVMm411dI538BDdTTbylbWxv8xwhjJQCgCZDVuJw==
-----END RSA PRIVATE KEY-----
```
#### **kid的生成**
首先从RSA私钥中提取公钥,保存到文件`public_key.pem`中
```
$ openssl rsa -in private_key.pem -out public_key.pem -pubout
```
然后将公钥文件由pem格式生成der格式
```
$ openssl rsa -pubin -inform PEM -in public_key.pem -outform DER -out public_key.der
```
然后对der格式的公钥文件做sha256哈希
```
$ sha256sum public_key.der
3b3def0af2c85f060fb90c71494dc3105ea8b5a5bfc822ae0b5c89a54152e706
```
去掉十六进制的哈希值后四位得到
```
3b3def0af2c85f060fb90c71494dc3105ea8b5a5bfc822ae0b5c89a54152
```
然后用base32(RFC4648)进行编码([base32在线编码网址](http://tomeko.net/online_tools/hex_to_base32.php?lang=en)),编码后得到
```
HM666CXSZBPQMD5ZBRYUSTODCBPKRNNFX7ECFLQLLSE2KQKS
```
每四位一组,中间用`:`隔开,得到kid的值
```
HM66:6CXS:ZBPQ:MD5Z:BRYU:STOD:CBPK:RNNF:X7EC:FLQL:LSE2:KQKS
```
- 安装
- 在线安装
- 离线安装
- 下载镜像
- 下载DockerHub镜像
- 下载Google镜像
- 阿里云镜像中心
- 下载ARM镜像
- 容器命名空间
- Linux命名空间概述
- 根据PID快速定位到容器
- 进入到容器的命名空间
- Dockerfile
- 基本语法
- 前台运行
- 镜像存储
- 本地存储
- Registry中的存储
- 如何判断两个镜像是否是同一个
- Registry
- Notification
- Auth
- 基本原理
- Token认证的设计
- API
- Pull镜像
- Push镜像
- Docker设置代理
- 日志
- 磁盘占用与清理
- Docker选项与K8S的Yaml
- 运维总结
- 常用命令
- DockerCompose
- 构建ARM版本
- 跨架构
- x86架构下构建arm64镜像
- Containerd
- ctr-crictl-nerdctl
- ctr
- Insecure-Registry
- Kata
- 构建OS镜像
- 进入到kata虚机