微信公众号:萝卜大杂烩 关注可了解更多的原创内容。问题或建议,请公众号留言或加本人微信; 如果你觉得文章对你有帮助,欢迎加微信交流
今天继续分享 Vue 系列,使用 JWT 来管理用户认证信息
认证:判定用户的合法性,一般是判断是否已经登陆 鉴权:判断用户的权限,一般是判断用户是否可以继续继续某个操作
今天先来看看认证相关
这个是 HTTP 协议中所带的基本认证功能。原理为在每个请求的 headers 中携带用户名和密码。 特点就是简单,但是却不是很安全。
将认证结果存储在浏览器的 cookie 中,后面通过检查 cookie 来校验认证信息。 特点是一次认证多次使用,但是却属于有状态的服务,对于分布式的服务端来说,管理 cookie 是个问题。
JWT(JSON Web Token) 是一种协议,它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。基本的原理是,第一次认证通过用户名密码,服务端签发一个 JSON 格式的 token。后续客户端的请求都携带这个 token,服务端仅需要解析这个 token,来判别客户端的身份和合法性。
使用 Python,可以很方便的生成一个 JWT 的 token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
secret_key = 'hardtoguess'
salt = 'hardtoguess'
access_token_expires_in = 1800
refresh_token_expires_in = 86400
def genTokenSeq(user):
u = User.query.filter_by(username=user).first()
if u:
userid = u.id
else:
return {'code': 422, 'message': '用户不存在'}
access_token_gen = Serializer(secret_key=secret_key, salt=salt, expires_in=access_token_expires_in )
refresh_token_gen = Serializer(secret_key=secret_key, salt=salt, expires_in=refresh_token_expires_in )
timtstamp = time.time()
access_token = access_token_gen.dumps({
"userid": userid,
"iat": timtstamp
})
refresh_token = refresh_token_gen.dumps({
"userid": userid,
"iat": timtstamp
})
data = {
"access_token": str(access_token, 'utf-8'),
"access_token_expire_in": access_token_expires_in ,
"refresh_token": str(refresh_token, 'utf-8'),
"refresh_token_expire_in": refresh_token_expires_in ,
}
return data
因为我们产生 token 是用来登陆的,所以我在 token 中增加了 userid,同时还增加了当前时间戳。这里产生了两个 token,分别是 access token 和 refresh token,access token 就是用户登陆成功后,发放给前端的,后面用户访问其他接口,都需要携带这个 token 来校验,refresh token 我们后面再解释。
解析 token 其实类似,只不过需要对不同的 token 错误做判断校验
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired, BadSignature, BadData
def validateToken(token):
s = Serializer(secret_key=secret_key, salt=salt)
try:
data = s.loads(token)
except SignatureExpired:
msg = 'toekn expired'
return {'code': 401, 'error_code': 'auth_01', 'message': msg}
except BadSignature as e:
encoded_payload = e.payload
if encoded_payload is not None:
try:
s.load_payload(encoded_payload)
except BadData:
msg = 'token tampered'
return {'code': 401, 'error_code': 'auth_02', 'message': msg}
msg = 'badSignature of token'
return {'code': 401, 'error_code': 'auth_03', 'message': msg}
except:
msg = 'wrong token with unknown reason'
return {'code': 401, 'error_code': 'auth_04', 'message': msg}
if 'userid' not in data:
msg = 'illegal payload inside'
return {'code': 401, 'error_code': 'auth_05', 'message': msg}
msg = 'user(' + str(data['userid']) + ') logged in by token.'
userId = data['userid']
return {'code': 200, 'error_code': 'auth_00', 'message': msg, 'userid': userId}
方便起见,所有的 token 错误,都返回了401这个错误码,其实是应该再细分下的。
再来看看前端需要做的事情,其实无非登陆成功后是把拿到的 token 保存起来,在调用其他接口时把 HTTP headers 中增加 token 信息就好了。
登陆接口
localStorage.setItem("accessToken", tokenInfo['access_token']);
localStorage.setItem("refreshToken", tokenInfo['refresh_token']);
localStorage.setItem("refreshTokenExpiryTime", tokenInfo['refresh_token_expire_in']);
localStorage.setItem("accessTokenExpiryTime", tokenInfo['access_token_expire_in']);
把 token 信息等存储在 localStorage 中。
设置 header 方法
function setToken() {
var JWT = 'jwt '
Axios.defaults.headers['Authorization'] = JWT + localStorage.getItem('accessToken');
}
在 header 中的 Authorization 中加入 jwt + token,后面所有的 API 请求,就都会带着这个 token 到后端了。
我们找一个前端用过的一个 javascript 函数,简单修改下
getData:function(){
//let data = {};
dealdata.getfile()
.then(res => {
console.log(res.data);
if(res.data.code == 200) {
if (res.data.data.length == 0){
this.files = []
}else{
this.files = res.data.data;
}
}else if(res.data.code == 401){
this.$message.error("登陆过期");
this.$router.push({path: '/login'});
}else{
console.log("API 调用错误");
}
})
.catch(function(error){
console.log("网络错误");
})
}
代码很简单,就是判断下后端返回的 code 值,如果为 401,则设置当前 url 到 /login
因为我们后期可能会有很多接口都需要用到函数 validateToken 来判断 token,为了方便优雅起见,使用装饰器就是最好的选择了 首先构造一个校验 token 的装饰器
def tokenRequired(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'Authorization' in request.headers:
split_token = request.headers['Authorization'].split(' ')
if len(split_token) == 2 and split_token[0] == 'jwt':
token = request.headers['Authorization'].split(' ')[1]
else:
return {'code': 401, 'message': 'authorize failed'}
else:
return {'code': 401, 'message': 'authorize failed'}
validator = validateToken(token)
if validator['code'] != 200:
return validator
elif validator['code'] == 200:
return f(*args, **kwargs)
return f(*args, **kwargs)
return decorated_function
在代码中,首先校验了 headers 的格式是否正确,如果正确,则开始判断函数 validateToken 的值,如果也通过了,才最终返回到被装饰的函数。
把装饰器装饰在 API 函数上
class FileListView(Resource):
@token.tokenRequired
def get(self):
try:
files = os.listdir(PYTEST_DIR)
file_list = []
i = 0
for f in files:
file_dict = {}
if f[-4:] == 'xlsx' and not f.startswith('~'):
file_dict["value"] = i
file_dict["label"] = f
file_list.append(file_dict)
i += 1
return {'code': 200, 'data': file_list}
except:
raise
这个函数也是我们以前用过的,现在该函数在接收到没有携带正确 token 的请求时,是无法正确返回数据的了。
token 在生成之后,是靠 expire 使其过期失效的。签发之后的 token,是无法收回修改的,因此涉及 token 的有效期是个难题,主要体现在如下两个问题上:
这里先给出实现方案,具体实现我们下次再细说
猜泥稀饭: