JWT是什么?
- JWT是json web token的缩写,一种基于token的json格式web认证方法,说白了就是一个很长的字符串
JWT原理是什么?
- 基本的原理是,第一次认证通过用户名密码,服务端签发一个json格式的token。后续客户端的请求都携带这个token,服务端仅需要解析这个token,来判别客户端的身份和合法性
JWT的作用和特点
- 由于http协议是无状态的,所以客户端每次访问都是新的请求。这样每次请求都需要验证身份,传统方式是用session+cookie来记录/传输用户信息,而JWT就是更安全方便的方式。它的特点就是简洁,紧凑和自包含,而且不占空间,传输速度快,而且有利于多端分离,接口的交互等等
- JWT的主要作用在于:可附带用户信息,后端直接通过JWT获取相关信息。 使用本地保存,通过HTTP Header中的Authorization位提交验证
JWT的组成
- 一个通常你看到的jwt,由以下三部分组成,它们分别是:
- 1.header:主要声明了JWT的签名算法;是一个描述JWT元数据的JSON对象
{
"alg": "HS256", alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256)
"typ": "JWT" typ属性表示令牌的类型,JWT令牌统一写为JWT
}
- 2.payload:主要承载了各种声明并传递明文数据;是存放有效信息的地方。
{
"iss": "http://shaobaobaoer.cn", iss: JWT的签发者,是否使用是可选的
"aud": "http://shaobaobaoer.cn/webtest/jwt_auth/", aud: 接收该JWT的一方,是否使用是可选的
"jti": "4f1g23a12aa", jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
"iat": 1534070547, iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的
"nbf": 1534070607, nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的
"exp": 1534074147, exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的
"uid": 1,
"data": {
"uname": "shaobao",
"uEmail": "[email protected]",
"uID": "0xA0",
"uGroup": "guest"
}
}
- 3.signture:拥有该部分的JWT被称为JWS,也就是签了名的JWS;没有该部分的JWT被称为nonsecure JWT 也就是不安全的JWT,此时header中声明的签名算法为none。三个部分用.分割。形如 xxxxx.yyyyy.zzzzz的样式。例如:
eyJhbGciOiJIUzUxMiIsImlhdCI6MTU4NTc5MTM2MCwiZXhwIjoxNTg1NzkxNjYwfQ.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInVzZXJfcGFzc3dkIjoiMTIzNDU2IiwidXNlcl9hZGRyZXNzIjoiMTI3LjAuMC4xOjgwODAiLCJ0aW1lb3V0IjozMDAsImlhdCI6MTU4NTc5MTM2MC4xMjgxMDN9.
qMxelHUKkqPj4NI357vPgC3Zi5u8f9132mYAqlhmnAnX6PjFRtD2_bmSlOoX4XaghlLl00xMgblO7LngH6jt6A
JWT 的签名算法有三种。
- 1.对称加密HMAC【哈希消息验证码】 HS256/HS384/HS512
这种加密方式没有公钥,私钥之分, 也就是只有一个密钥, 这种加密方式适用于: 服务器将生成的jwt发送给接收方, 接收方将其返回给服务器, 服务器解析 jwt, 完成身份验证.
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法生成签名
- 2.非对称加密RSASSA【RSA签名算法】RS256/RS384/RS512
- 3.ECDSA【椭圆曲线数据签名算法】 ES256/ES384/ES512
不同的签名适用场景
- 对称的算法HMAC适用于单点登录,一对一的场景中。速度很快。
- 但是面对一对多的情况,比如一个APP中的不同服务模块,需要JWT登录的时候,主服务端【APP】拥有一个私钥来完成签名即可,而用户带着JWT在访问不同服务模块【副服务端】的时候,副服务端只要用公钥来验证签名就可以了。从一定程度上也减少了主服务端的压力。
- 当然,还有一种情况就是不同成员进行开发的时候,大家可以用统一的私钥来完成签名,然后用各自的公钥去完成对JWT的认证,也是一种非常好的开发手段。
- 构建一个没有多个小型“微服务应用程序”的应用程序,并且开发人员只有一组的,选择HMAC来签名即可。其他情况下,尽量选择RSA
JWT的几点说明
- 1、JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
- 2、当JWT未加密时,一些私密数据无法通过JWT传输。
- 3、JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
- 4、JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。
- 5、JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。
- 6、为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
实例代码
import time
import configparser
from scripts.response import Resp
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature
class JWTKeyGen:
secret_key = '4180da82-0c83-4d66-ab14-e2793573ecaa' # 密钥,默认不可变,更改需要与服务器沟通
salt = '16fcf475-5180-4916-83c1-5ff79616eaa9' # 加密盐,随机字符串,默认不可变,更改需要与服务器沟通
expires = 300 # token过期时长 单位:S
jti = '4f1g23a12aa' # jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
space = 600 # 间隔时间 单位:S 注意用于生成nbf:nbf = iat(签发时间) - space
config_path = "../config/jwt.conf" # 配置文件路径
'''
获取配置文件中指定key的value值
: param key: 配置参数名称
: return: 返回key对应的value字符串
'''
def get_config_info(self, key):
conf = configparser.ConfigParser()
conf.read(JWTKeyGen.config_path)
try:
value = conf.get("JWT", key)
if value == '':
value = self.compare_key(key)
except Exception:
value = self.compare_key(key)
return value
'''
比对key的名称,不同的key赋不同的value
: param key: 配置参数名称
: return: 返回key对应的value字符串
'''
def compare_key(self, key):
if key == 'secret_key':
value = self.secret_key
elif key == 'salt':
value = self.salt
elif key == 'expires':
value = self.expires
elif key == 'jti':
value = self.jti
else:
value = self.space
return value
'''
生成token
:param username: 用户名
:param password: 用户密码
:param address: 服务器地址
:return: 返回token字符串
'''
def gen_token_seq(self, username, address):
expires = self.get_config_info("expires")
# 序列化
s = Serializer(
salt=self.get_config_info("salt"),
secret_key=self.get_config_info("secret_key"),
expires_in=int(self.get_config_info("expires"))
)
timestamp = time.time() # 获取当前时间戳,单位 S
# 有效数据信息
json_str = {
'iat': timestamp, # iat(issuedat): 在什么时候签发的(UNIX时间),是否使用是可选的
"iss": "www.xxx.com", # iss: JWT的签发者,是否使用是可选的
"aud": "www.xxx.com", # aud: 接收该JWT的一方,是否使用是可选的
"jti": self.get_config_info("JTI"), # jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击,是否使用是可选的
# nbf(Not Before):如果当前时间在nbf时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;是否使用是可选的
"nbf": int(timestamp) - int(self.get_config_info("space")),
"exp": int(expires), # exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的
"uid": 1, # 用户id,此处写成死值,如有需要可查库获取,是否使用是可选的
"data": {
'username': username,
# 'password': password,
'address': address,
}
}
return s.dumps(json_str)
'''
token解析
:param token: jwt字符串
:return: 返回解密后的有效数据
'''
def token_auth(self, token):
if token is None:
return Resp().get_error('参数token为空')
s = Serializer(salt=self.get_config_info("salt"), secret_key=self.get_config_info("secret_key")) # 序列化
try:
data = s.loads(token) # token解析
except SignatureExpired: # token过期
return Resp().get_error('该token已过期')
except BadSignature: # 签名出错
return Resp().get_error('签名出错')
except: # 其他异常
return Resp().get_error('未知原因出错')
# 判断签名是否被篡改
if ('username' not in data['data']) or ('address' not in data['data']):
return Resp().get_error('载荷数据不合法, 非法操作')
# 获取有效信息数据并返回
info = {
"username": data['data']['username'],
# "password": data['data']['password'],
"address": data['data']['address']
}
return Resp().get_ok('OK', data=info)
'''
token校验
:param token: token字符串
:return True / False
'''
def verify_token(self, token):
login_info = self.token_auth(token)
if login_info.get('code') == 200:
username = login_info.get('data').get('username')
# password = login_info.get('data').get('password')
address = login_info.get('data').get('address')
return True if self.gen_token_seq(username, address) == token else False
else:
return login_info
if __name__ == "__main__":
name = "admin"
# password = "123456"
address = "127.0.0.1:8080"
token = JWTKeyGen().gen_token_seq(name, address)
print("生成的token----:" + str(token))
login_info = JWTKeyGen().token_auth(token)
if login_info.get('code') == 200:
print("用户名:" + login_info.get('data').get('username'))
# print("密码:" + login_info.get('data').get('password'))
print("url:" + login_info.get('data').get('address'))
print("消息:" + login_info.get('msg'))
else:
print("消息:" + login_info.get('msg'))
[JWT]
; 密钥,默认不可变,更改需要与服务器沟通
secret_key = 4180da82-0c83-4d66-ab14-e2793573ecaa
; 加密盐,随机字符串,默认不可变,更改需要与服务器沟通
salt = 16fcf475-5180-4916-83c1-5ff79616eaa9
; token过期时长 单位:S
expires = 300
; jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
jti = 4f1g23a12aa
; 间隔时间 单位:S 注意用于生成nbf:nbf = iat(签发时间) - space
space = 600
class Resp:
'''
出错返回
:param msg: 错误消息说明
:param data: 返回的数据
:return {
"code": 500,
"data": data,
"msg": msg
}
'''
def get_error(self, msg, data=None):
res = {
"code": 500,
"data": data,
"msg": msg
}
return res
'''
正常返回
:param msg: 返回消息说明
:param data: 返回的数据
:return {
"code": 200,
"data": data,
"msg": msg
}
'''
def get_ok(self, msg, data=None):
res = {
"code": 200,
"data": data,
"msg": msg
}
return res