我司是使用 Jira 管理项目的 tasks、subtasks、issues。近期我要做一个 cli 工具,需要调用 jira rest api 获取某个 task 以及它下面的 subtasks 信息。查询 Jira 官网可知它提供了以下鉴权方式:
我选择的是 OAuth。简单解释下 OAuth:资源的拥有者(Owner)通过账号密码去访问资源(tasks、subtasks、issues),现在有一个第三方(consumer)也想访问这些资源,直接把账号密码给它的话,首先是不安全,其次没法控制第三方的访问权限。所以生成一个 token,在 token 上做权限控制,把它给到第三方,实现安全且有控制的访问。
Jira OAuth 鉴权 4 步骤
- 配置 Jira
- 生成一个 RSA 公钥/私钥对
- 创建一个 Application Link
- 创建 Client
- 授权
- 发送请求
注意:Application links use OAuth(version 1.0a) with RSA-SHA1 signing for authentication。
我先在 postman 上试了试:Authorization Type 选择 OAuth1.0(为什么不选 OAuth1.0a?因为没有🐶),发现 Signature Method 中没有 RSA-SHA1 选项,查了下发现 postman 版本低了,升级到最新版本(v9.29.6)后有了 RSA-SHA1 选项(此为第 1 坑),如下图,输入相关字段成功获取 tasks 信息。我发现 Version 字段是可以编辑的,试着改成 1.0a 发现 response 没了!
RSA Private Key
Jira OAuth 鉴权步骤中生成的 RSA 私钥 .pem 内容如下,也就是 consumerSecret
-----BEGIN RSA PRIVATE KEY-----
sa1R8yi9lnZW3+FzxChaniztcs6P3qKX77h8ab54U0+ke7AMNZ1gTdZCCCrXiUkG
6dTi4L58qoj1EpN12A4lBe12Az/zKp2cN4Kh+atKDtyCOFHkc0gkbJMeAQIDAQAB
NEKpbKoRaraHHN7CsN2iEvcxvxcvxcv4545ghghgfhKFKDWDSCIvQHyFIfH0PIr5
GIf1ys2uFj79V/cjxfresgqz2vjvKIaB2dj5nTaw5riBbNkCQQDQ9oSmA/95vsPt
8cX9LF2lzJ2OpSYfII3u4SR/EBED5wtp6eeBWXV67Q==
-----END RSA PRIVATE KEY-----
该文件的格式如下:每一行后面都有一个换行符
-----BEGIN RSA PRIVATE KEY-----\n
xxx\n
yyy\n
ddd\n
-----END RSA PRIVATE KEY-----\n
当我直接把 .pem 复制到 js 对象属性时,如下图:
secret 的格式变成了下面这样,从第二行开始前面多了两个空格,打印出来就很明显了
-----BEGIN RSA PRIVATE KEY-----\n
xxx\n
yyy\n
ddd\n
-----END RSA PRIVATE KEY-----\n
当使用 crypto
创建 RSA-SHA1
签名就报了 error:0908F066:PEM routines:get_header_and_data:bad end line
(此处是第 2 坑)
import crypto from 'crypto';
crypto.createSign('RSA-SHA1').update(baseString).sign(JIRA_CONFIG.consumerSecret, 'base64');
下图是第 3 坑,然鹅,我写这篇的时候已经忘了当时怎么踩坑里了(过了快两周了)
Axios with OAuth
Jira OAuth 鉴权步骤中发送请求使用的是 java 的例子,我们来看看 Axios + OAuth 如何鉴权?
Axios 鉴权信息是放在 headers 中的,如下:
axios.get(url, {
headers: {
Authorization: `OAuth ${token}` // token 怎么生成呢?
}
})
经过 Jira OAuth 鉴权步骤后,我们可以得到 consumerKey, consumerSecret, token, tokenSecret。接下来,我们需要生成 Axios 需要的 token:
Jira 使用的是 OAuth-1.0a,我查到的 npm 包有 oauth, oauth-1.0a。
npm oauth 如何生成 token?
import { OAuth } from 'oauth';
// 以下 key, token, secret 都是乱敲的,别妄想了。。。
export const JIRA_CONFIG = {
consumerKey: 'OAuthKey',
consumerSecret:
'-----BEGIN RSA PRIVATE KEY-----\nsa1R8yi9lnZW3+FzxChaniztcs6P3qKX77h8ab54U0+ke7AMNZ1gTdZCCCrXiUkG\n6dTi4L58qoj1EpN12A4lBe12Az/zKp2cN4Kh+atKDtyCOFHkc0gkbJMeAQIDAQAB\nNEKpbKoRaraHHN7CsN2iEvcxvxcvxcv4545ghghgfhKFKDWDSCIvQHyFIfH0PIr5\nGIf1ys2uFj79V/cjxfresgqz2vjvKIaB2dj5nTaw5riBbNkCQQDQ9oSmA/95vsPt\n8cX9LF2lzJ2OpSYfII3u4SR/EBED5wtp6eeBWXV67Q==\n-----END RSA PRIVATE KEY-----',
authToken: 'esgqz2vjvKIaB2dj5nTaw5riBbNkCQQDQ9',
authTokenSecret: 'lBe12Az/zKp2cN4Kh+atKDtyCOFHkc0g',
};
export const JIRA_CONSUMER = new OAuth(
'https://jdog.atlassian.com/plugins/servlet/oauth/request-token',
'https://jdog.atlassian.com/plugins/servlet/oauth/access-token',
JIRA_CONFIG.consumerKey,
JIRA_CONFIG.consumerSecret,
'1.0',
null,
'RSA-SHA1',
);
const oauthHeader = JIRA_CONSUMER.authHeader(
url,
JIRA_CONFIG.authToken,
JIRA_CONFIG.authTokenSecret,
'GET',
);
axios.get(url, {
headers: {
Authorization: oauthHeader,
}
})
- consumerSecret 内容
npm oauth-1.0a 如何生成 token?
import OAuth from 'oauth-1.0a';
import crypto from 'crypto';
export const JIRA_CONFIG = {
consumerKey: 'OAuthKey',
consumerSecret:
'-----BEGIN RSA PRIVATE KEY-----\nsa1R8yi9lnZW3+FzxChaniztcs6P3qKX77h8ab54U0+ke7AMNZ1gTdZCCCrXiUkG\n6dTi4L58qoj1EpN12A4lBe12Az/zKp2cN4Kh+atKDtyCOFHkc0gkbJMeAQIDAQAB\nNEKpbKoRaraHHN7CsN2iEvcxvxcvxcv4545ghghgfhKFKDWDSCIvQHyFIfH0PIr5\nGIf1ys2uFj79V/cjxfresgqz2vjvKIaB2dj5nTaw5riBbNkCQQDQ9oSmA/95vsPt\n8cX9LF2lzJ2OpSYfII3u4SR/EBED5wtp6eeBWXV67Q==\n-----END RSA PRIVATE KEY-----',
authToken: 'esgqz2vjvKIaB2dj5nTaw5riBbNkCQQDQ9',
authTokenSecret: 'lBe12Az/zKp2cN4Kh+atKDtyCOFHkc0g',
};
function hashFunctionSha1(baseString: string, key: string) {
return crypto
.createSign('RSA-SHA1')
.update(baseString)
.sign(JIRA_CONFIG.consumerSecret, 'base64');
}
export const JIRA_CONSUMER = new OAuth({
consumer: {
key: JIRA_CONFIG.consumerKey,
secret: JIRA_CONFIG.consumerSecret,
},
signature_method: 'RSA-SHA1',
hash_function: hashFunctionSha1,
version: '1.0',
});
const oauthHeader = JIRA_CONSUMER.toHeader(
JIRA_CONSUMER.authorize(
{
url,
method: 'GET',
},
{
key: JIRA_CONFIG.authToken,
secret: JIRA_CONFIG.authTokenSecret,
},
),
);
axios.get(url, {
headers: {
...oauthHeader,
},
})
注意:private key 最好保密,写在 js 里打包之后也是能被发现滴。但我们这个 cli 只在内网用,所以就这么着了!
题外话
为啥 Authorization 里加个 Bearer 呢?
什么是 RSA-SHA1 签名?
RSA
- RSA 是一种非对称加密算法,1977 年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的,RSA 就是他们三人姓氏开头字母拼在一起组成的。
- 非对称加密算法需要两个密钥:公开密钥(publickey: 简称公钥)和私有密钥(privatekey: 简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
- 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将公钥公开,需要向甲方发送信息的其他角色(乙方)使用该密钥(甲方的公钥)对机密信息进行加密后再发送给甲方;甲方再用自己私钥对加密后的信息进行解密。甲方想要回复乙方时正好相反,使用乙方的公钥对数据进行加密,同理,乙方使用自己的私钥来进行解密。
- 另一方面,甲方可以使用自己的私钥对机密信息进行签名后再发送给乙方;乙方再用甲方的公钥对甲方发送回来的数据进行验签。
SHA-1
- 英语:Secure Hash Algorithm 1,中文名:安全散列算法1,是一种密码散列函数。SHA-1 将任意大小的输入转化为固定长度的输出(160 位),通常的呈现形式为 40 个十六进制数。基于散列的算法是不可逆的,即无法通过散列值倒推出原始数据。
- 散列函数存在碰撞,碰撞即有两个不同的数据,他们的散列值相同(SHA1值相同),但是很少得,达到千万分之一。
- Hash:散列,或音译为哈希。
SHA1('irene') -> 2ec1ddb5b29133e23d1e8722efead92bce315ef7 // 40 个十六进制数
RSA vs SHA-1
- SHA-1 (can't be reversed)无法被解密,RSA 是可以被解密的。查资料的时候发现一些在线解密网站声称能解密 SHA-1,对于一些简单的原始数据可能能被解密出来,但是复杂的原始数据就不一定了,see
- RSA 的效率不高,但 SHA 的效率高。
RSA-SHA1
先对输入进行散列,然后用私钥进行加密,输出一个固定长度的数字签名。
---------------------------------这是一条分界线--------------------------------
用周董骗骗赞🥺
疯狂赶今年的 KPI 😓......掐指一算,还有大半(单鸭)