flask token认证
在前后端分离的项目中,我们现在多半会使用token认证机制实现登录权限验证。
token通常会给一个过期时间,这样即使token泄露了,危害期也只是在有效时间内,超过这个有效时间,token过期了,就需要重新生成一个新的token。
如何生成token呢?
1、创建用户数据库,本文会使用flask-SQLAlchemy(ORM)去管理数据库:
首先创建一个用户模型:包括了用户昵称,账号(邮箱或者电话号码等),密码及拥有的权限
class User(Base):
id = Column(Integer, primary_key=True)
nickname = Column(String(30), nullable=False)
account = Column(String(30), nullable=False)
_password = Column("password", String(100), nullable=False)
auth = Column(SmallInteger, default=1) @property
def password(self):
return self._password @password.setter
def password(self, row):
self._password = generate_password_hash(row) @staticmethod
def register_by_email(nickname, account, password):
with db.auto_commit():
user = User()
user.nickname = nickname
user.account = account
user.password = password
db.session.add(user) @staticmethod
def checkUser(email, password):
# 验证用户名是否存在
user = User.query.filter_by(account=email).first_or_404()
res = user.checkPassword(password)
if not res:
raise AuthFailed()
scope = "adminScope" if user.auth=="" else "scope"
return {"uid":user.id, "scope":scope} def checkPassword(self, raw):
if not self._password:
return False
# check_password_hash将raw加密后和_password比较
p = generate_password_hash(raw)
print(p==self._password)
return check_password_hash(self._password, raw) def delete(self):
self.status = ""
由于安全原因,数据库的密码是一定不能明文保存的,所以此处将用户名进行了加密
本文使用的werkzeug.security下面的generate_password_hash()对密码进行的加密,我们定义了password.setter方法,当在设置密码时,会调用generate_password_hash(password)加密密码,并将其赋值给_password
当验证密码时,会调用werkzeug.security下面的check_password_hash(hashpwd, raw) 对用户传递过来的密码和加密后的密码进行比对,如果正确返回True
2、注册
当前端传递过来用户名,密码时进行注册时,我们需要对用户名和密码进行如下基本验证
1)非空性及长度等基本校验
2)用户名是否已经存在
邮箱注册form:
class EmailRegisterForm(RegisterForm):
nickname = StringField(validators=[DataRequired(), length(3,30)])
account = StringField(validators=[DataRequired(message="account can not be blank"), length(
min=3, max=32, message="account length wrong"), Email(message="format wrong")])
password = StringField(validators=[DataRequired()]) def validate_account(self, value):
user = User.query.filter_by(account=value.data).first()
if user:
raise ParamsError(msg = "用户已存在")
当验证成功后,会调用我们在User模型下面定义的register_by_email() 方法进行注册。
@api.router("/register", methods=["POST"])
def register():
data = request.json
form = RegisterForm(data=data).validate_for_api()
promise = {
ClientType.REGISTER_EMAIL:_register_by_email,
ClientType.REGISTER_MOBILE:_register_by_mobile()
}
promise[form.type.data]()
return Success()
def _register_by_email():
form = EmailRegisterForm(data=request.json).validate_for_api()
nickname = form.nickname.data
account = form.account.data
password = form.password.data
User.register_by_email(nickname, account,password)
现在我们使用postman发送一条注册请求

如果用户已经存在,会返回400

3、登录,生成token
生成token的方式有很多种,如产生一个固定长度的随机字符串,和用户名密码及过期时间一起存储在数据库中,这样token就是一个普通的字符串,可以方便的和其他字符串验证比较并可以检查是否过期
比较复杂一点的做法就是,不要将token存储在数据库,而是使用数字签名作为token,这样做的好处是经过用户数字签名的token是可以防止篡改的。
flask使用与数字签名类似的方法去实现加密的token,我们可以直接使用itsdangerous库去实现。
生成token,需要用到itsdangerous下面的TimedJSONWebSignatureSerializer
首先我们实例化一个Serializer,并将我们的秘钥SECRET_KEY和过期时间作为参数,返回一个TimedJSONWebSignatureSerializer类型对象
然后调用TimedJSONWebSignatureSerializer对象的dumps方法,将我们想要写入到token中的信息以字典形式传递进去即可。
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer def generate_auth_token(uid, type, scope, expiration=7200):
serializer = Serializer(current_app.config["SECRET_KEY"], expires_in=expiration)
token = serializer.dumps({"uid":uid, "type":type.value, "scope":scope})
return token
当前端传递用户名,密码到服务端时,服务端校验用户存在并且密码正确时候,就会调用generate_auth_token函数,生成token
值得注意的一点是,这里生成的token是二进制的,所以我们在返回给前端时,需要将二进制解码token.decode("ascii")
后面用户在访问需要登录才能访问的的接口时,就不需要再登录,只需要将token传递过来即可。
1)验证用户名是否存在,此方法作为静态方法放在User模型下
@staticmethod
def checkUser(email, password):
user = User.query.filter_by(account=email).first_or_404()
res = user.checkPassword(password)
if not res:
raise AuthFailed()
scope = "adminScope" if user.auth=="" else "scope"
return {"uid":user.id, "scope":scope}
2)校验密码是否匹配
def checkPassword(self, raw):
if not self._password:
return False
# check_password_hash将raw加密后和_password比较
return check_password_hash(self._password, raw)
3)校验通过后,调用generate_auth_token方法生成token
@api.router("/", methods=["POST"])
def get_token():
data = request.json
form = EmailLoginForm(data=data).validate_for_api()
type = form.type.data
promise = {
ClientType.REGISTER_EMAIL:User.checkUser
}
identify = promise[ClientType(type)](form.account.data, form.password.data)
expiration = current_app.config["EXPIRATION"]
token = generate_auth_token(identify["uid"], type,identify["scope"], expiration)
r = {
"token":token.decode("ascii")
}
return jsonify(r)
4、token认证
如用户想要获取用户信息,这个是要登录后才能访问的接口,我们可以使用一个装饰器 @auth.login_required 保护,即表示只有正常登录的用户才可以访问
这个装饰器用到了flask_httpauth库下面的HTTPBasicAuth
auth = HTTPBasicAuth
HTTP Basic Authentication 协议没有具体要求必须使用用户名密码进行验证,HTTP头可以使用两个字段去传输认证信息,对于token,我们只需要将token作为用户名传递过去即可,密码字段可以不填
@auth.verify_password将作为@auth.login_required的中校验密码的回调函数被调用。
我们前面生成token的时候,用到了我们自定义了SECRET_KEY加密,同样解密也需要使用我们的秘钥SECRET_KEY,加密调用的是serializer.dumps(),解密对应的需要使用serializer.loads()
调用serializer.loads(token)时,如果捕捉到下面两个错误:
BadSignature:签名错误,签名可能被篡改
SignatureExpired:签名已过期
表示验证token失败,直接抛出自定义异常,如果没有捕捉到错误,表示,验证通过。可以从中取得前面加密的用户信息,并将信息保存在g变量中,留做他用。
这里的g变量和request一样,都是代理模式的实现,而且是线程隔离的,所以也不用担心多个请求线程导致数据错乱。
@auth.verify_password
def check_authorization(token, pwd):
user_info = check_auth_token(token)
if not user_info:
return False
else:
g.user = user_info
return True def check_auth_token(token):
serialzer = Serializer(current_app.config["SECRET_KEY"])
try:
s = serialzer.loads(token)
except BadSignature:
raise AuthFailed(msg="token is invalid", error_code=1004)
except SignatureExpired:
raise AuthFailed(msg="token is expired", error_code=1004)
uid = s["uid"]
type = s["type"]
scope = s["scope"]
return user(uid, type, scope)
【补充】
某些要求比较严谨的验证,还可以将设备mac地址等信息加入都token中
获取设备ip mac等信息的方法:
import socket
host_name = socket.gethostname()
ip = socket.gethostbyname(host_name)
import uuid
mac = uuid.UUID(int=uuid.getnode()).hex[-12:].upper()
flask token认证的更多相关文章
- Flask扩展实现HTTP令牌token认证HTTPTokenAuth
Token认证 在restful设计中,用户认证模式通常使用json web token,而不会使用传统的HTTP Basic认证(传入账号密码) token认证模式如下:在请求header中加入to ...
- 基于Token认证的多点登录和WebApi保护
在文章中有错误的地方,或是有建议或意见的地方,请大家多多指正,邮箱: linjie.rd@gmail.com 一天张三,李四,王五,赵六去动物园,张三没买票,李四制作了个假票,王五买了票,赵六要直接F ...
- 【翻译】asp.net core2.0中的token认证
原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...
- Java实现基于token认证
随着互联网的不断发展,技术的迭代也非常之快.我们的用户认证也从刚开始的用户名密码转变到基于cookie的session认证,然而到了今天,这种认证已经不能满足与我们的业务需求了(分布式,微服务).我们 ...
- laravel5.7 前后端分离开发 实现基于API请求的token认证
最近在学习前后端分离开发,发现 在laravel中实现前后台分离是无法无法使用 CSRF Token 认证的.因为 web 请求的用户认证是通过Session和客户端Cookie的实现的,而前后端分离 ...
- python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾 1. 五个葫芦娃和三行代码 APIView(views.View) 1. 封装了Django的request - request.query_params --> 取URL中的参数 ...
- django使用RestFramework的Token认证
今天实现的想法有点不正规: Django Rest framework的框架的认证,API都运行良好. 现在是要自己写一个function来实现用户的功能. 而不是用Rest 框架里的APIVIEW这 ...
- 基于nginx的token认证
Nginx 的 token 认证是基于集成了 nginx+lua 的 openresty 来实现的. 环境: centos 7 部署方式: 增量部署(不影响原 nginx 版本) 版本: openre ...
- Token认证登录以及权限控制
IdentityServer4实现Token认证登录以及权限控制 相关知识点 不再对IdentityServer4做相关介绍,博客园上已经有人出了相关的系列文章,不了解的可以看一下: 蟋蟀大神的: ...
随机推荐
- Spring Boot 2.x(十四):整合Redis,看这一篇就够了
目录 Redis简介 Redis的部署 在Spring Boot中的使用 Redis缓存实战 寻找组织 程序员经典必备枕头书免费送 Redis简介 Redis 是一个开源的使用 ANSI C 语言编写 ...
- C#程序员知识体系
[https://blog.csdn.net/zj735539703/article/details/50409476] 基础部分 C# 基础语法 OOP的概念,面向对象的理解 继承 封装 多态 AS ...
- 【转载】ASP.NET中Server.MapPath方法获取网站根目录总结
在ASP.NET网站应用程序中,可以通过Server.MapPath方法来获取跟服务器有关的目录信息,如获取网站的根目录.获取当前代码文件所在的目录路径.获取当前代码所在路径的上级路径等.Server ...
- php 时间戳最大值
今天遇到一个bug,获取有效期值错误,在定位跟踪后发现有效期有值,如下: $expireDate = ; //2037-08-16 09:30:48 但是在该时间戳的基础上加上1 year后, $ex ...
- "PECS原则"几篇好文章
<? extends T>和<? super T>Java 泛型中的PECS原则(copy源码样例)
- Java开发笔记(二十二)神奇的冒号
Java中的标点符号主要有两类用途,一类是运算符,包括加号+.减号-.乘号*.除号/.取余号%.等号=.大于号>.小于号<.与号&.或号|.非号!.异或号^等等,另一类则是分隔符, ...
- Java_设计模式之享元模式
1.关于享元模式 享元模式有点类似于单例模式,都是只生成一个对象被共享使用.享元模式主要目的就是让多个对象实现共享,减少不会要额内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的 ...
- input type date 解决移动端显示placeholder
在最近的一个项目中使用到了html5的一个新标签属性,type="date"时,发现placeholder属性失效无法使用. 如果是这样的效果,那么客户体验是可想而知的差了. 最后 ...
- 2019-02-10 扩展Python控制台实现中文反馈信息
"中文编程"知乎专栏原文地址 参考了周蟒的实现, 运行效果如下: $ python3 解释器.py Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 ...
- 自然底数e的意义是什么?
自然底数e的意义是什么? https://mp.weixin.qq.com/s?__biz=MzA5ODUxOTA5Mg==&mid=2652553811&idx=1&sn=0 ...