因为期末考试加上种种原因,没有及时更新博客,趁着假期时间,将欠下的债补上。
在之前的博客系统代码中前端路由控制是通过登陆请求,成功返回uuid给前端,然后前端通过验证是否有uuid来实现前端路由的权限控制。在之后设计中,我意识到两个重要的问题
问题一: 当api获取方式只有我一个人知道的时候,这种api访问无控制问题或许无关紧要,但是当有其他人知道,并借此来攻击我的服务器,那么我的服务器就只能凉凉~~
问题二:博客的访问用户分为匿名访客、普通会员、管理员,不同角色可以访问api的权限是不同的,需要控制其访问
-
为什么使用jwt
当想到这种问题,却没有解决方案时,第一反应时在网上搜,庆幸的是(手动狗头),在我之前很多人遇到这种问题,并且有一套成熟的解决方案,那么就是oauth授权框架,它与本文jwt有一定联系,却不能相提并论,毕竟一个是协议,一个是框架,毕竟我只是一个小博客项目,没有精力也没有必要使用一整套框架,来实现一个小小的业务场景。所以我使用了jwt协议,来保障api的访问安全。
-
jwt协议
JSON Web Token (JWT)
JWT在标准中是这么定义的:
JWT是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器,服务器验证用户提交信息信息的合法性;如果验证成功,会产生并返回一个Token(令牌),用户可以使用这个token访问服务器上受保护的资源。
一个token的例子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
一个完整的token组成分为三部分它们由.分割:header、payload、Signature签名。具体jwt的知识请点击这个链接,阮一峰大佬 -
jwt具体实现
我选择了pyjwt库进行token的生成与解析 pyjwt文档
token的生成:
@staticmethod def encode_token(user_name): try: headers = { "typ": "JWT", "alg": "HS256", } payload = { "headers": headers, 'exp': datetime.utcnow() + timedelta(days=0, seconds=10), 'iat': datetime.utcnow(), 'iss': 'yker', 'data': { 'user_name': user_name } } return jwt.encode(payload, 'secret', 'HS256') except Exception as e: return e
token的解析:
# token验证 @staticmethod def decode_token(token): try: payload = jwt.decode(token, 'secret', options={'verify_exp': False}) if 'data' in payload and 'user_name' in payload['data']: return payload else: raise jwt.InvalidTokenError except jwt.ExpiredSignatureError: return "Token过期" except jwt.InvalidTokenError: return "无效的Token"
访问控制的解决----RBAC用户角色权限
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
-
具体实现
jwt与RBAC的结合
token及身份认证:@staticmethod def identify(): params = request.headers.get('Authorization') path = request.path methods = request.method if params is not None and params != 'null': auth_token = Auth.decode_token(request.headers.get('Authorization')) if isinstance(auth_token, str): return { 'flag': 'error', 'msg': auth_token } if not auth_token or auth_token['headers']['typ'] != 'JWT': result = { 'flag': 'error', 'msg': '请传递正确的验证头信息'} else: user = User.query.filter_by(name=auth_token['data']['user_name']).first() if user is None: result = { 'flag': 'error', 'msg': '找不到该用户信息'} else: if Auth.route_interception(path, methods, user.role_id): result = { 'flag': 'success', 'msg': '请求成功' } else: result = { 'flag': 'error', 'msg': '无权限' } else: if Auth.anonymous_authentication(path, methods): result = { 'flag': 'success', 'msg': '匿名请求成功'} else: result = { 'flag': 'error', 'msg': '没有提供认证token'} return result
@staticmethod def anonymous_authentication(path, methods): # 匿名访问控制 anonymous_authentication = [{ 'url': '/api/article', 'method': 'GET' }, { 'url': '/api/comment', 'method': 'GET' }, { 'url': '/api/kind', 'method': 'GET' }, { 'url': '/api/tag', 'method': 'GET' }] # 去掉多余url参数 if path.count('/') > 2: path = path[0:path.rfind('/')] # for item in anonymous_authentication: if item.get('url') == path and item.get('method') == methods: return True else: return False
@staticmethod def route_interception(path, methods, role): print(path, methods, role) # 去掉多余url参数 if path.count('/') > 2: path = path[0:path.rfind('/')] permission = Permission.query.\ filter_by(url=path, method=methods, role=role).first() if permission is not None: return True else: return False
总结
对于匿名用户我并没有将其放进角色表中,而是直接写死在代码中,具体原因是在token认证identify()函数中,由于匿名用户没有token所以会返回没有提供认证token的错误,这不符合匿名用户api请求的设计,所以对匿名用户没有token且又需要api的需求设计了anonymous_authentication()函数,对其进行权限处理,也没想专门为匿名用户新建一个表。或许在以后我会新建一个匿名用户表来储存用户ip,根据ip创建临时身份,以此返回一个token,而匿名用户携带这个token就可以访问权限允许的api,站在巨人的肩膀上真的会让我们看的更高,走的更远。加油! 2019