Webman实战教程:使用JWT认证插件实现跨域安全认证

简介

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。

跨域认证的问题

互联网服务用户认证一般流程

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

认证流程

  • 用户在前端输入usernamepassword,然后点击Enter。
  • 前端(在用户的浏览器中运行)发送一个usernamepassword我们的API在一个特定的URL(以申报tokenUrl="token")。
  • API 检查username和password,并用“令牌”响应(我们还没有实现任何这些)。“令牌”只是一个包含一些内容的字符串,我们稍后可以使用它来验证此用户。通常,令牌设置为在一段时间后过期。因此,用户稍后将不得不再次登录。如果代币被盗,风险就小了。它不像一个永久有效的密钥(在大多数情况下)。 前端将该令牌临时存储在某处。
  • 用户单击前端以转到前端 Web 应用程序的另一部分。
  • 前端需要从 API 获取更多数据。但它需要对该特定端点进行身份验证。因此,为了使用我们的 API 进行身份验证,它会发送Authorization一个值为Bearer加上令牌的标头。如果令牌包含foobar,则Authorization标头的内容将为:Bearer foobar注意:中间是有个空格

使用JWT

安装

代码语言:javascript
复制
composer require tinywan/jwt

生成令牌

代码语言:javascript
复制
$user = [
    'id'  => 2022, // 这里必须是一个全局抽象唯一id
    'name'  => 'Tinywan',
    'email' => 'Tinywan@163.com'
];
$token = Tinywan\Jwt\JwtToken::generateToken($user);
var_dump(json_encode($token));

输出(json格式)

代码语言:javascript
复制
{
    "token_type": "Bearer",
    "expires_in": 36000,
    "access_token": "eyJ0eXAiOiJAUR-Gqtnk9LUPO8IDrLK7tjCwQZ7CI...",
    "refresh_token": "eyJ0eXAiOiJIEGkKprvcccccQvsTJaOyNy8yweZc..."
}

参数描述

参数

类型

描述

示例值

token_type

string

Token 类型

Bearer

expires_in

int

凭证有效时间,单位:秒

36000

access_token

string

访问凭证

XXXXXXXXXXXXXXXXXXXX

refresh_token

string

刷新凭证(访问凭证过期使用 )

XXXXXXXXXXXXXXXXXXXX

已支持方法

1、获取当前uid

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::getCurrentId();

2、获取所有扩展字段

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::getExtend();

3、获取自定义字段

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::getExtendVal('email');

4、刷新令牌(通过刷新令牌获取访问令牌)

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::refreshToken();

5、获取令牌有效期剩余时长(单位:秒)

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::getTokenExp();

6、单设备登录。默认是关闭,开启请修改配置文件config/plugin/tinywan/jwt

代码语言:javascript
复制
'is_single_device' => true,

7、获取当前用户信息(模型)。需要插件大于版本 >=1.2.4

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::getUser();

注意:该配置项目'user_model'为一个匿名函数,匿名函数参数$uid为当前登录用户id,默认返回空数组,可以根据自己项目ORM定制化自己的返回模型

ThinkORM 配置

代码语言:javascript
复制
'user_model' => function($uid) {
 // 返回一个数组
 return \think\facade\Db::table('resty_user')
  ->field('id,username,create_time')
  ->where('id',$uid)
  ->find();
}

LaravelORM 配置

代码语言:javascript
复制
'user_model' => function($uid) {
 // 返回一个对象
 return \support\Db::table('resty_user')
  ->where('id', $uid)
  ->select('id','email','mobile','create_time')
  ->first();
}

8、令牌清理

代码语言:javascript
复制
Tinywan\Jwt\JwtToken::clear();

注:只有配置项 is_single_devicetrue 才会生效

已支持签名算法

JWT 最常见的几种签名算法(JWA):HS256(HMAC-SHA256)RS256(RSA-SHA256) 还有 ES256(ECDSA-SHA256)

JWT 算法列表如下

代码语言:javascript
复制
+--------------+-------------------------------+--------------------+
   | "alg" Param  | Digital Signature or MAC      | Implementation     |
   | Value        | Algorithm                     | Requirements       |
   +--------------+-------------------------------+--------------------+
   | HS256        | HMAC using SHA-256            | Required           |
   | HS384        | HMAC using SHA-384            | Optional           |
   | HS512        | HMAC using SHA-512            | Optional           |
   | RS256        | RSASSA-PKCS1-v1_5 using       | Recommended        |
   |              | SHA-256                       |                    |
   | RS384        | RSASSA-PKCS1-v1_5 using       | Optional           |
   |              | SHA-384                       |                    |
   | RS512        | RSASSA-PKCS1-v1_5 using       | Optional           |
   |              | SHA-512                       |                    |
   | ES256        | ECDSA using P-256 and SHA-256 | Recommended+       |
   | ES384        | ECDSA using P-384 and SHA-384 | Optional           |
   | ES512        | ECDSA using P-521 and SHA-512 | Optional           |
   | PS256        | RSASSA-PSS using SHA-256 and  | Optional           |
   |              | MGF1 with SHA-256             |                    |
   | PS384        | RSASSA-PSS using SHA-384 and  | Optional           |
   |              | MGF1 with SHA-384             |                    |
   | PS512        | RSASSA-PSS using SHA-512 and  | Optional           |
   |              | MGF1 with SHA-512             |                    |
   | none         | No digital signature or MAC   | Optional           |
   |              | performed                     |                    |
   +--------------+-------------------------------+--------------------+

The use of "+" in the Implementation Requirements column indicates
that the requirement strength is likely to be increased in a future
version of the specification.

可以看到被标记为 Recommended 的只有 RS256 和 ES256。

对称加密算法

插件安装默认使用HS256 对称加密算法。

HS256 使用同一个「secret_key」进行签名与验证。一旦 secret_key 泄漏,就毫无安全性可言了。因此 HS256 只适合集中式认证,签名和验证都必须由可信方进行。

非对称加密算法

RS256 系列是使用 RSA 私钥进行签名,使用 RSA 公钥进行验证。

公钥即使泄漏也毫无影响,只要确保私钥安全就行。RS256 可以将验证委托给其他应用,只要将公钥给他们就行。

以下为RS系列算法生成命令,仅供参考

RS512
代码语言:javascript
复制
ssh-keygen -t rsa -b 4096 -E SHA512 -m PEM -P "" -f RS512.key
openssl rsa -in RS512.key -pubout -outform PEM -out RS512.key.pub
RS512
代码语言:javascript
复制
ssh-keygen -t rsa -b 4096 -E SHA354 -m PEM -P "" -f RS384.key
openssl rsa -in RS384.key -pubout -outform PEM -out RS384.key.pub
RS256
代码语言:javascript
复制
ssh-keygen -t rsa -b 4096 -E SHA256 -m PEM -P "" -f RS256.key
openssl rsa -in RS256.key -pubout -outform PEM -out RS256.key.pub

具体算法配置请参考 config/plugin/tinywan/jwt/app.php 配置文件

视频地址

不懂的同学可以了解一下视频,会有详细的说明哦