php jwt安全吗,关于 jwt 你应该知道的事情

JWT(JSON Web Token)是一种轻量级的身份验证规范,常用于授权和鉴权。本文详细介绍了JWT的组成(头部、载荷、签名)、生成过程以及安全性保障。通过一个邀请用户入群的场景,解释了如何使用JWT创建安全的邀请链接,并讨论了JWT的数据泄露风险和secret的重要性。此外,还提供了一个简单的PHP JWT实现示例。
摘要由CSDN通过智能技术生成

什么是 jwt ?

JWT 全称叫 JSON Web Token, 是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。

jwt 使用场景

jwt 用图广泛,例如授权、鉴权等。具体一点的话,假如我们有一个 A 用户想要邀请某用户进入自己的群组,此时 A 用户需要生成一条邀请链接,链接内容大致如下: https://host/group/{group_id}/invite/{invite_user}

此时这个链接点击进去虽然可以实现让用户加入群组,但是用户可以随意更改这个链接的参数,例如改改 group 后面的ID,从而加入其他任意群组,改改 invite 后面的邀请人等等操作。所以这种 URL 并不是安全的,那么这种情况下,我们就可以使用 jwt 来实创建一个安全的邀请链接了。

首先 URL 要简单改一下, https://host/group/invite/{token}

可以看到我们去掉了 groupId 和 inviteUser 参数,添加了一个 token 参数,可想而知, groupId 和 inviteUser 应该是被包含进 token 里面了,如何实现这个看似很神奇的 token 呢? 我们来看看 jwt 的原理吧。

jwt 的组成、原理及实现

在讲 jwt 原理之前得先知道 jwt 由哪些东西组成。

jwt 组成

一个 JWT 实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

头部 (Header)

JWT 需要一个头部,用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个 JSON 对象,如:

{

"typ": "JWT",

"alg": "md5"

}

将上面的 json 字符串使用 base64 进行编码后,可以得到一下内容,我们称其为 JWT 的头部(Header)。

eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==

载荷(Payload)

我们先将上面的邀请入群的操作描述成一个 JSON 对象。其中添加了一些其他的信息,帮助今后收到这个 JWT 的服务器理解这个JWT。

{

"sub": "1",

"iss": "http://host/group/invite",

"iat": 1451888119,

"exp": 1454516119,

"nbf": 1451888119,

"jti": "37c107e4609ddbcc9c096ea5ee76c667",

"group_id": 1,

"invite_user": "A"

}

这里面的前6个字段都是由JWT的标准所定义的。

sub: 该 JWT 所面向的用户

iss: 该 JWT 的签发者

iat(issued at): 在什么时候签发的 token

exp(expires): token 什么时候过期

nbf(not before):token 在此时间之前不能被接收处理

jti:JWT ID为web token 提供唯一标识

将上面的 json 字符串使用 base64 进行编码后,可以得到一下内容,我们称其为 JWT 的载荷(Payload)。

eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9

签名(Signature)

在签名之前我们需要先得到用于签名的字符串, 将头部和载荷使用 . 进行拼接(头部在前), 得到用于签名的字符串

eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9

然后使用签名方法对用于签名的字符串进行签名, 得到如下字符串,即 签名(Signature)

NDljMzljOTkyOGNmYWU1NGEyZDYzMTk5NTNlNGEwZDA=

最后把用于签名的字符串和签名使用 . 进行拼接(签名在后), 即可得到 一个完整的 token。但是,此时的

token 没有带上签发者特有的标志,是可以被伪造的,至于如何解决这个问题我们下面 jwt 具体实现会讲。

jwt 原理

jwt 如何保证安全 ?

上面说完 jwt 组成,相信你已经知道 jwt 大概是个啥子东西了 --- 就是一个字符串!!!

那么这个字符串如何保证不被篡改呢 ? 这里就要引入 secret 了。

回到上面的例子,邀请用户入群这个场景,虽然我们上面把 参数改成了 token 这种形式,但是你可能会发现,这样的 token 别人捕获了之后,任然可以自己伪造一个类似的 token ,因为此时的签名(Signature)并没有签发者特有的身份信息,所有数据都是明文的,所以这样签名是不安全的,应该加上 secret 进行签名。

签发者需要准备一个可以确认自己身份的字符串,这个字符串我们称之为 secret 。以 md5 作为签名方法为例(并不建议使用 md5 作为签名方法),我们只需要将上面准备的 用于签名的字符串简单的与 secret 进行拼接,然后进行 md5 计算,这时候得到的签名是受 secret 值影响的,所以即便他人捕获了之后 token,他仍然不能随意篡改 token 的内容,因为他不知道 secret 和拼接方法,故此时的 token 是安全的,不可被恶意篡改的。

$signatureString = 'pen'; // 原始数据

$secret = 'apple'; // 签发者 secret

$originSignature = md5($signatureString .'-'. $secret);

print_r($signature); // apple-pen

$signatureString = 'pen'; // 原始数据

$secret = 'pineapple'; // 不一样的 secret

$fakeSignature = md5($signatureString .'-'. $secret);

print_r($signature); // pineapple-pen

// 可以看到不一样的 secret 会生成完全不一样的签名,这样我们的数据就可以保证不能被随意篡改了~

jwt 传输的数据会泄露 ?

是的,jwt 的头部和载荷字段都可以被解码(base64 属于编码,是可以被解码的)。所以并不建议用 jwt 传输敏感信息,例如密码,因为这很容易被捕获后解码,从而被窃取。

secret 一个字符串不足以描述签发者信息 ?

我们可以将 签发者信息描述成一个 json ,然后对这个 json 字符串进行编码,这样同样可以得到一个 secret 字符串。

jwt 实现

先来一个最粗暴的 jwt 实现

最简单粗暴的 jwt for php 实现

class JWT

{

protected $headers;

protected $payload;

/**

* @return array

*/

public function getHeaders(): array

{

return $this->headers;

}

/**

* @return array

*/

public function getPayload(): array

{

return $this->payload;

}

public function __construct(array $headers, array $payload)

{

$this->setHeaders($headers);

$this->setPayload($payload);

}

public function setHeaders(array $headers): void

{

$this->headers = $headers;

}

public function setPayload(array $payload): void

{

$this->payload = $payload;

}

/**

* 获取用于签名的字符串

*

* @return string

*/

public function signatureStr(): string

{

$headersStr = $this::encodeStr(json_encode($this->headers));

$payloadStr = $this::encodeStr(json_encode($this->payload));

return "{$headersStr}.{$payloadStr}";

}

/**

* 编码

*

* @param string $string

*

* @return string

*/

protected static function encodeStr(string $string): string

{

return rtrim(strtr(base64_encode($string), '+/', '-_'), '=');

}

/**

* 解码

*

* @param string $string

*

* @return string

*/

protected static function decodeStr(string $string): string

{

return base64_decode(strtr($string, '-_', '+/'));

}

/**

* 签名,此时的 secret 为 qbhy

*

* @param string $string

*

* @return string

*/

protected static function signature(string $string): string

{

return md5($string . 'qbhy');

}

/**

* 校验签名

*

* @param string $signStr

* @param string $sign

*

* @return bool

*/

protected static function checkSignature(string $signStr, string $sign): bool

{

return static::signature($signStr) === $sign;

}

/**

* 生成 token

*

* @return string

*/

public function token(): string

{

$signStr = $this->signatureStr();

$token = $signStr . '.' . $this::signature($signStr);

return $token;

}

/**

* 从 token 中获取数据

*

* @param string $token

*

* @return \App\Modules\JWT\JWT

* @throws \App\Modules\JWT\JWTException

*/

public static function fromToken(string $token): JWT

{

$arr = explode('.', $token);

if (count($arr) !== 3) {

throw new JWTException('token 错误');

}

if (!static::checkSignature("{$arr[0]}.{$arr[1]}", $arr[2])) {

throw new JWTException('签名错误');

}

$headers = json_decode(static::decodeStr($arr[0]), true);

$payload = json_decode(static::decodeStr($arr[1]), true);

return new static($headers, $payload);

}

}

simple-jwt

这里先安利一下我写的一个基于 php 的 jwt 扩展包 --- 96qbhy/simple-jwt , 这个包实现了完整的 jwt 规范,开箱即用,你可以基于 96qbhy/simple-jwt 来给你的应用添加 jwt 相关功能。

我把 simple-jwt 拆分成,Encoder(编码器) 、Encrypter(签名器) 、JWT、JWTManager 四部分,你可以自行扩展 Encoder、Encrypter,从而实现自己的编码和加密方法,感兴趣的同学可以去 github 看看源码 96qbhy/simple-jwt 。有问题欢迎与我讨论,同时欢迎 Issue 和 PR 。

如有错误欢迎指出,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值