Django REST framework 登录之JWT

2,101

写在前面

Django REST framework 全文搜索实战

DRF 自带的账号体系为Django admin的登录系统,需要结合admin的表单才能使用,所以今天聊一聊基于DRF的JWT的实现

本文的代码是以 Django REST framework (二)为基础开发的

扩展项目

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密.

JWT 是什么?

jwt是一串被加密的字符串,共有三个部分组成

  • header 头部 声明类型以及加密算法
  • payload 数据域 主要包含自定义的数据比如作者 过期时间等
  • signature 签名 是由 header + payload 根据header中指定的加密算法计算而来,为了防止篡改

其中 Payload 也是一个 JSON 对象,官方规定了7个官方字段,供选用。

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

展示具体的demo详情

# jwt string
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

# HEADER:ALGORITHM & TOKEN TYPE
{
  "alg": "HS256",
  "typ": "JWT"
}

# PAYLOAD:DATA
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

# VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
) 

1. 安装依赖

pipenv install djangorestframework-jwt

2. 配置项目


# demo/settings.py

REST_FRAMEWORK = {
    # ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    #...
}

# demo/urls.py

from rest_framework_jwt.views import obtain_jwt_token
#...

urlpatterns = [
    '',
    # ...
    url(r'^jwt-token-verify/', verify_jwt_token),
    url(r'^api-token-auth/', obtain_jwt_token),
]

3. 查看结果

jwt 登录账号


# 1. 测试返回结果
$ curl -X POST -H "Accept: application/json;indent=4"  -d "username=admin&password=admin" http://localhost:8000/jwt-token-auth/ 

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjIzMDM5MDE5LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSJ9.uAXhriWcnDrY6-JyeO1Wb8Un1X4tUE8Xb4pEBmtGJaI"
}

# 2. 解析之后的参数为
# HEADER:ALGORITHM & TOKEN TYPE
{
  "typ": "JWT",
  "alg": "HS256"
}
# PAYLOAD:DATA

{
  "user_id": 1,
  "username": "admin",
  "exp": 1623036515,
  "email": "admin@admin.com"
}
# VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload)
)

验证token

$ curl --location --request POST 'http://localhost:8000/jwt-token-verify/' --header 'Content-Type: application/json;indent=4' --data-raw '{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjIzMTMxNzM2LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSJ9.NvcNYUIQhJGZFTr7dYftu_8uWgHBhSqw4t_3etF9AS8"}'

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjIzMTMxNzM2LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSJ9.NvcNYUIQhJGZFTr7dYftu_8uWgHBhSqw4t_3etF9AS8"}

请求携带JWT

$ curl -H  "Accept: application/json;indent=4" -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjIzMDM5MDE5LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSJ9.uAXhriWcnDrY6-JyeO1Wb8Un1X4tUE8Xb4pEBmtGJaI"  http://localhost:8000/api/article/1/
{
    "id": 1,
    "creator": "admin",
    "tag": "现代诗",
    "title": "如果",
    "content": "今生今世 永不再将你想起\n除了\n除了在有些个\n因落泪而湿润的夜里 如果\n如果你愿意"
}

4. 更多配置

# settings.py 新增
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=24),
}

更多配置以及使用见文档

  • 配置加密方式,使用非对称加密配置公钥私钥;
  • 自定义token生成逻辑(在不使用Django的用户表时非常有用)
  • ······

总结

优点:

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT。
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

缺点:

  1. 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  2. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  3. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

综上 在微服务系统中服务端不需要记录session的信息,所有的认证通过前端携带的jwt,后端对jwt进行鉴权,这样不仅可以避免session共享的问题,还可以有效的避免跨域和csrf等问题。

参考资料