做费网站,深圳网站开发公司,wordpress安装ssl,房产网站制作方案JWT
基本概念
在用户登录后#xff0c;我们需要在不同请求之间记录用户的登录状态#xff0c;常用方式一般有三种#xff1a;Cookie#xff0c;Session和Token。
这里我们使用第三种Token令牌方式来实现认证鉴权#xff0c;采用Json Web Token认证机制#xff08;简称…JWT
基本概念
在用户登录后我们需要在不同请求之间记录用户的登录状态常用方式一般有三种CookieSession和Token。
这里我们使用第三种Token令牌方式来实现认证鉴权采用Json Web Token认证机制简称jwt。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的特别适用于分布式站点的单点登录SSO场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息该token也可直接被用于认证也可被加密。jwt官网https://jwt.io/
jwt规范https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token
JWT的构成 JWT就一段由三段信息构成的字符串将这三段信息文本用.拼接一起就构成的。就像这样
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ第一部分我们称它为头部header),第二部分我们称其为载荷payload, 类似于飞机上承载的物品)第三部分是签证signature).
jwtToken f{header}.{payload}.{signature}header
jwt的头部承载两部分信息
typ: type的缩写声明token的类型值一般可以是 JWT 或 Bear 。alg: algorithm的缩写声明token的第三方部分签证的加密算法通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON
{typ: Bear,alg: HS256
}然后将头部进行base64.b64urlencode()编码构成了第一部分头部。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9python代码实现过程
import base64,json
data {typ: JWT,alg: HS256
}header base64.b64encode(json.dumps(data).encode()).decode()# 各个语言中都有base64加密解密的功能所以我们jwt为了安全需要配合第三段加密payload
载荷payload就是jwt存放有效信息的部分。这个名字像是特指飞机上承载的货仓这些有效信息包含三种不同类型的数据
标准声明公共声明私有声明
标准声明 (官方提出建议但不强制使用) iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间这个过期时间必须要大于签发时间 nbf: 定义在什么时间之后该jwt可以正常使用。 iat: jwt的签发时间 jti: jwt的唯一身份标识主要用来作为一次性token往往采用UUID字符串或随机字符串来充当。 以上是JWT规范中提供的7个官方字段开发者根据自己的业务进行选用。
公共声明公共的声明可以添加任何的信息一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息因为该部分在客户端直接可以查看。
私有声明私有声明是服务端和客户端所共同定义的声明一般使用类似ace算法进行非对称加密和解密的意味着该部分信息可以归类为明文信息。
定义一个payloadjson格式的数据
{sub: 1234567890, // 时间戳exp: 3422335555, // 时间戳name: John Doe,admin: true,
}然后将其进行base64.b64encode() 编码得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9python代码实现过程
import base64,json
data {sub: 1234567890,exp: 3422335555,name: John Doe,admin: True,info: 232323ssdgerere3335dssss
}payload base64.b64encode(json.dumps(data).encode()).decode()# 各个语言中都有base64编码和解码所以我们jwt为了安全需要配合第三段签证来进行加密保证jwt不会被人篡改。signature
JWT的第三部分叫签证信息主要用于辨真伪防篡改。签证信息使用加密算法生成公式
secret_key 秘钥 # 只保存服务端不能外泄
signature SHA256(base64.b64encode(header) . base64.b64encode(payload),secret_key) python代码实现过程
import sys, json, base64, time, hmacif __name__ __main__:# 头部data {typ: JWT, alg: HS256}header base64.b64encode(json.dumps(data).encode()).decode()# 载荷data {sub: 1234567890, exp: 3422335555, name: John Doe, admin: True,info: 232323ssdgerere3335dssss}payload base64.b64encode(json.dumps(data).encode()).decode()# 签证生成jwt token 提供给客户端# from django.conf import settings# secret settings.SECRET_KEYsecret django-insecure-(_qtd5edmhm%2rdsgqc3wis_k*3cbk-k2gpg3qx)z6rpsign base64.b64encode(f{header}.{payload}.encode())signature base64.b64encode(hmac.digest(secret.encode(), sign, digestsha256)).decode()jwt ff{header}.{payload}.{signature}print(jwt)# 将这三部分用.连接成一个完整的字符串,构成了最终的jwt:# feyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJleHAiOiAiMzQyMjMzNTU1NSIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogdHJ1ZSwgImluZm8iOiAiMjMyMzIzc3NkZ2VyZXJlMzMzNWRzc3NzIn0.3OnGXAx5wWA5AjxyewICSn5Hirz1tXfzxOc4tns4elM注意 secret是保存在服务器端的jwt的签发生成代码也是在服务器端的secret就是用来进行jwt的签发和jwt的验证 所以它应该是服务端的私钥在任何场景下都不应该流露出去而且应该在每次服务端更新维护后及时更新。 一旦第三方得知这个secret, 那就意味着他们绕过服务端伪造jwt了。 优缺点
优点
实现分布式集群的单点登陆非常方便Token实际保存在客户端所以我们可以分担服务端的存储压力。jwt不仅可用于认证还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数jwt的构成非常简单字节占用很小所以它是非常便于传输的。
缺点
jwt保存在客户端我们服务端只认jwt不识别客户端。 解决方案1. 设置客户端唯一登陆解决方案2. 绑定客户端的标记符和IP机器码 jwt可以设置过期时间但是因为jwt保存在了客户端所以对于过期时间不好调整一旦签发不可控。 解决方案1设置短有效期例如30分、15分钟、10分钟之类。解决方案2生成jwt的时候提供给客户端之前先在内存一般使用内存数据库redis而不是变量备份jwt每次用户访问需要登录身份数据时把token去内存中验证一样。
使用jwt实现认证流程
所谓的认证流程实际上就是用户登录的过程。 python代码实现认证流程代码
import sys, json, base64, time, hmacif __name__ __main__:# 模拟客户端提交的tokenclient_token feyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJleHAiOiAiMzQyMjMzNTU1NSIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogdHJ1ZSwgImluZm8iOiAiMjMyMzIzc3NkZ2VyZXJlMzMzNWRzc3NzIn0.3OnGXAx5wWA5AjxyewICSn5Hirz1tXfzxOc4tns4elM# 把客户端提交的token分割成三段头部、载荷、签证header, payload, signature client_token.split(.)# 验证是否过期了先基于base64接着使用json解码提供载荷中的过期时间进行比较payload_data json.loads(base64.b64decode(payload.encode()))exp int(payload_data.get(exp, 0))now int(time.time())if exp now:print(token已经过期!)sys.exit() # 退出程序实际开发中应该时响应代码给客户端不会继续往下执行了。secret django-insecure-(_qtd5edmhm%2rdsgqc3wis_k*3cbk-k2gpg3qx)z6rp# 与生成token时一样的秘钥和数据再次生成一个签证new_signature hmac.digest(secret.encode(), sign, digestsha256)# 拿客户端提交上面的token中的签证进行base64解码得到原始的签证signature base64.b64decode(signature)# 通过compare_digest比较两者是否吻合if hmac.compare_digest(signature, new_signature):print(认证通过)else:print(认证失败token被串改)
基本使用
开发中除非找不到否则我们可以直接使用第三方已经开源的模块来完成相关的功能。大部分要求使用第三方模块是必须star数量150。
依赖库安装
# python-jose 用于生成和检验JWT令牌
pip install jwt
pip install python-joseJWT基本使用
生成一个随机的密钥用于对JWT令牌进行签名加密的。终端执行命令如下
openssl rand -hex 32
# eac77e4e9a9a767b792779132e84ea37b1f4c31bec56714607f617a3fbdfbd53创建JWT需要的相关配置项settings.py代码
# 加密数据所使用的秘钥[盐值]
SECRET_KEY eac77e4e9a9a767b792779132e84ea37b1f4c31bec56714607f617a3fbdfbd53
# 设定JWT令牌签名算法
ALGORITHM HS256
# 设置令牌过期时间变量单位秒
ACCESS_TOKEN_EXPIRE_MINUTES 30 * 60创建JWT工具类utils.py代码
from typing import Optional
from datetime import timedelta, datetime
import settings
from jose import jwt
import uuidclass JWT(object):JWTError jwt.JWTErrorExpiredSignatureError jwt.ExpiredSignatureErrordef create_token(self, data: dict, expire_time: Optional[timedelta] None):生成Token:param data: 需要进行JWT令牌加密的用户信息解密的时候会用到:param expire_time: 令牌有效期单位秒:return: tokennow_time datetime.utcnow()if expire_time:expire now_time timedelta(secondsexpire_time)else:expire now_time timedelta(secondssettings.ACCESS_TOKEN_EXPIRE_TIME)payload {exp: expire,iat: now_time,nbf: now_time,jti: str(uuid.uuid4())}payload.update(data)token jwt.encode(payload, settings.SECRET_KEY, algorithmsettings.ALGORITHM)return tokendef verify_token(self, token: str) - dict:验证token:param token: 客户端发送过来的token:return: 返回用户信息payload jwt.decode(token, settings.SECRET_KEY, algorithmssettings.ALGORITHM)return payloadif __name__ __main__:密码加密与验证# hashing Hashing()# hashed_pwd hashing.hash(123456)# print(hashed_pwd) # 加密后要保存到数据库中的哈希串# # 把原密码和加密后的哈希串进行配对验证通过则返回结果为True# ret hashing.verify(123456, hashed_pwd)# print(ret)JWTjwt_tool JWT()try:# 正确使用token jwt_tool.create_token({username: admin, sex: True})print(token)data jwt_tool.verify_token(token)print(data)# # 因为Token过期导致验证失败# token jwt_tool.create_token({username: admin, sex: True}, -300)# print(token)# data jwt_tool.verify_token(token)# print(data)# # 因为Token被串改导致验证失败# token jwt_tool.create_token({username: admin, sex: True})# print(token)# data jwt_tool.verify_token(token[:-1])# print(data)except (jwt_tool.ExpiredSignatureError, jwt_tool.JWTError) as e:print(验证失败, e)基于自定义中间件创建JWT中间件实现用户身份认证
async def jwt_middleware(request: Request, call_next):try:token: str request.headers[Authorization].split()[1]payload jwt_took.verify(token)id: str payload.get(id)# 查询数据库是否存在当前用户user await models.User.filter(idid).first(id)if user is None:raise HTTPException(status_codeHTTP_401_UNAUTHORIZED, detailInvalid authentication credentials)request.user userexcept (jwt_tool.ExpiredSignatureError, jwt_tool.JWTError):raise HTTPException(status_codeHTTP_401_UNAUTHORIZED, detailInvalid authentication credentials)response await call_next(request)return response注册JWT中间件代码
app.add_middleware(jwt_middleware)