从零开始,构建前后端分离的博客系统三(jwt认证协议与RBAC用户角色权限的实践)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38356149/article/details/86633770

因为期末考试加上种种原因,没有及时更新博客,趁着假期时间,将欠下的债补上。

在之前的博客系统代码中前端路由控制是通过登陆请求,成功返回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
    
    • 匿名用户api权限
      @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
    
    • 管理员&&普通会员api权限验证
    @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

猜你喜欢

转载自blog.csdn.net/qq_38356149/article/details/86633770