Django使用pyjwt实现Token跨域认证登录过程实践

更多内容请点击 我的博客 查看,欢迎来访。

官方文档: https://pyjwt.readthedocs.io/en/latest/installation.html

JWT定义

Json web token (JWT), 根据官网的定义,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

[外链图片转存失败(img-qli33Ad0-1562813971810)(https://www.starmeow.cn/media/blog/images/2019/07/BLOG_20190711_105308_70.png “博客图集BLOG_20190711_105308_70.png”)]

  1. 应用(或者客户端)想授权服务器请求授权。例如,如果用授权码流程的话,就是/oauth/authorize
  2. 当授权被许可以后,授权服务器返回一个access token给应用
  3. 应用使用access token访问受保护的资源(比如:API)

JWT特点

  • 体积小,因而传输速度快
  • 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
  • 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。
  • 支持跨域验证,可以应用于单点登录。
  • jwt 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  • jwt在不加密的情况下,不能将涉密信息写入jwt

与session共享机制的对比

  1. 代码侵入,在每个服务上必须有存取session鉴权判断的代码;
  2. 不安全,虽然redis等可以很方便的进行分布式部署,假若session服务器挂掉,系统便完全无法使用,不满足分布式系统中的高可用;
  3. 内存无法控制,若用户访问量激增极可能冲破内存,对内存要求高;

与之相反这些正是JWT的优势所在:

  1. 无代码侵入 ,只需要配置一个认证服务,其他服务可以无需关注权限,并可以提高到API网关进行权限拦截;
  2. JWT不依赖session,对内存无要求,大量的访问也从容应对,不易产生硬件瓶颈;
  3. 可以在JWT中存储角色等信息,减少数据库查询或无需查询;

安装pyjwt

pip install pyjwt

测试jwt生成和解码

Django的 Python Console中测试

>>> from django.conf import settings
>>> import jwt

>>> key = settings.SECRET_KEY  # 这个可以自定义密钥,为了方便直接使用Django的密钥

加密

# 加密
>>> encoded = jwt.encode({'username': 'admin', 'site': 'http://testdomain.starmeow.cn'}, key, algorithm='HS256')
# 可以查看生成的Token
>>> encoded
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic2l0ZSI6Imh0dHA6Ly9zdWliaWFuLnN0YXJtZW93LmNuIn0.5CzNoZ2aWyNYYnImrJvGJuLZlJbUeCVTg-_PFq_XF7o'
# 生成的是byte类型,可转换为字符串
>>> encoded.decode('utf-8')
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic2l0ZSI6Imh0dHA6Ly9zdWliaWFuLnN0YXJtZW93LmNuIn0.5CzNoZ2aWyNYYnImrJvGJuLZlJbUeCVTg-_PFq_XF7o'

{'username': 'admin', 'site': 'http://testdomain.starmeow.cn'}, key, algorithm='HS256'

三个参数:

  • 第一个是payload,是一个Json对象,主要用来存放有效的信息,例如用户名,过期时间等等所有你想要传递的信息;
  • 第二个是密钥,这里是读取配置文件中的SECRET_KEY配置变量,这个秘钥主要用在下文Signature签名中,服务端用来校验Token合法性,这个秘钥只有服务端知道,不能泄露;
  • 第三个是生成Token的算法。

payload,这是认证的依据,也是后续解析token后定位用户的依据,需要包含特定用户的特定信息,如下面示例中注册了data声明,data声明中用户的usernamenickname等,在“用户鉴权”方法中,解析token完成后要利用这个username来查找并返回用户信息给用户。这里的data声明是我们自己加的,pyjwt内置注册了以下几个声明(建议但不强制使用):

  • “exp”: 过期时间,是按当地时间确定,所以设置时要使用utc时间。
  • “nbf”: 表示当前时间在nbf里的时间之前,则Token不被接受
  • “iss”: token签发者
  • “aud”: 接收者
  • “iat”: 发行时间

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic2l0ZSI6Imh0dHA6Ly9zdWliaWFuLnN0YXJtZW93LmNuIn0.5CzNoZ2aWyNYYnImrJvGJuLZlJbUeCVTg-_PFq_XF7o

生成的Token:

  • 一个用两个点(.)分割的长字符串
  • 点分割成的三部分分别是Header头部,Payload负载,Signature签名:Header.Payload.Signature
  • 第一部分Header和第二部分Payload只是对原始输入的信息转成了base64编码,第三部分Signature是用header+payload+secret_key进行加密的结果

解码

解码第一部分Header,包括类别(typ)、加密算法(alg):通常直接使用 HMAC SHA256

>>> import base64
>>> base64.b64decode("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")
b'{"typ":"JWT","alg":"HS256"}'

解码第二部分Payload(加==:如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?此时,需在原数据后面添加1个或2个零值字节,使其字节数是3的倍数。然后,在编码后的字符串后面添加1个或2个等号“=”,表示所添加的零值字节数。解码的时候,会自动去掉。)
第二部分主要存放有效信息,由于这里用的是可逆的base64 编码,所以第二部分的数据实际上是明文的。应该避免在这里存放不能公开的隐私信息。

>>> base64.b64decode("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic2l0ZSI6Imh0dHA6Ly9zdWliaWFuLnN0YXJtZW93LmNuIn0==")
b'{"typ":"JWT","alg":"HS256"}{"username":"admin","site":"http://testdomain.starmeow.cn"}'

三部分一起解码,可以看到最后一部分被加密了无法查看

>>> base64.b64decode("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwic2l0ZSI6Imh0dHA6Ly9zdWliaWFuLnN0YXJtZW93LmNuIn0.5CzNoZ2aWyNYYnImrJvGJuLZlJbUeCVTg-_PFq_XF7o==")
b'{"typ":"JWT","alg":"HS256"}{"username":"admin","site":"http://testdomain.starmeow.cn"}9\x0b3hgf\x96\xc8\xd6\x18\x9c\x89\xab&\xf1\x89\xb8\xb6e%\xb5\x1e\tT\xe0

验证Token

# 解密Token
>>> decoded = jwt.decode(encoded, key, algorithms='HS256')
>>> decoded
{'username': 'admin', 'site': 'http://testdomain.starmeow.cn'}

到的payload可以跟原来生成payload进行比较来验证token的有效性。

加入自己的数据建议使用格式

将自定义的数据放在data中。

# 设置payload保存的信息
payload = {
    'exp': timezone.now() + datetime.timedelta(minutes=3),  # 设置过期时间:https://pyjwt.readthedocs.io/en/latest/usage.html#expiration-time-claim-exp
    'iat': timezone.now(),  # 设置发布时间:https://pyjwt.readthedocs.io/en/latest/usage.html#issued-at-claim-iat
    'data': {'username': username, 'nickname': nickname}  # 添加自定义的数据,保存在data中
}
# 加密
encoded = jwt.encode(payload, key, algorithm='HS256')
# 解密
decoded = jwt.decode(encoded, key, algorithms='HS256')

Django使用Token认证

说明:A应用为www,B应用为testdomain

子域名 testdomain.starmeow.cn 想要请求 www.starmeow.cn/login/ 认证用于登录,涉及到跨域请求,可以把 testdomain.starmeow.cn 当作一个独立的应用。

前端Ajax Post登录获取Token

渲染一个模板页面,加载jquery登录

这里直接修改的普通视图,先判断session,当用户在这个域没登陆时(因为没在session没在这个域中对这个键赋值,一般是没有值的)。
再来尝试获取Token,由于前端通过AuthorizationToken请求会传过来Token的值,或者直接获取COOKIEs中的Token值,解析里面的用户名,解码成功后返回用户名。

def get_request_username(request):
    """
    先从session中获取登录用户名,如果没获取到,再尝试从Token中解码用户名
    :param request:
    :return:
    """
    username = request.session.get('session_simple_user_name', '')
    if not username:
        http_authorization = request.META.get('HTTP_AUTHORIZATION')
        # print('超文本传输协议:', http_authorization)  # 如果是Token认证,就是:Token code???????code
        if http_authorization:
            # 尝试token认证登录
            method, code = http_authorization.split(' ')  # 空格分割字符串
            print('从HTTP_AUTHORIZATION中获取Token')
        else:
            code = request.COOKIES.get('simpleauth_token', '')
            print('从COOKES中获取Token')
            # print(code)
        try:
            import jwt
            from django.conf import settings

            decoded = jwt.decode(code, settings.SECRET_KEY, algorithm='HS256')
            username = decoded.get('data').get('username')  # 获取用户名:model中生成Token时放在payload的data中的数据
        except:
            pass
    return username


class PhaseGroupBuy(View):
    def get(self, request, phase_id):
        print('团购登记主页')

        editable = False  # 可编辑
        simpleauth_url = get_simpleauth_url(request)  # 获取登录地址的url函数,相当于只返回一个域名

        # 获取当前登录用户
        # simple_user = SimpleUser.objects.get(username=request.session.get('session_simple_user_name', ''))
        request_username = get_request_username(request)
        if request_username == '':
            # 如果没有获取到请求的用户,将cookie中的内容删除
            response = render(request, 'group_buy_v0_3/group-buy.html', locals())
            response.delete_cookie('simpleauth_nickname', path='/')
            response.delete_cookie('simpleauth_token', path='/')
            response.delete_cookie('simpleauth_username', path='/')
            return response

        print('获取到用户名:', request_username)

        # 其他代码

jquery中进行判断cookie值

如果cookie中没有simpleauth_username值,则强制显示模态框,要求用户登录

        //登录相关
        let simpleauth_token = $.cookie('simpleauth_token');

        function show_login_modal() {
            $('#tokenAuthLoginModal').modal({
                show: true,
                backdrop: "static",//点击空白处不关闭对话框
                keyboard: false //esc键盘不关闭.
            })
        }

        //console.log(simpleauth_token);
        //进入页面如果没获取到token就登录显示
        if (simpleauth_token === undefined || simpleauth_token === '') {
            show_login_modal();
        }

模态框代码如下


<div class="modal fade" id="tokenAuthLoginModal" tabindex="-1" role="dialog" aria-labelledby="tokenAuthLoginModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-xl">
        <div class="modal-content modal-login-bg">
            <div class="modal-header">
                
            div>
            <div class="modal-body">
                <div class="row w-100">
                    <div class="col-lg-4 mx-auto">
                        <div class="auth-form-light text-left p-5">
                            <div class="brand-logo">
                                <img src="{% static 'group_buy_v0_3/images/logo.png' %}" style="max-width: 120px">
                            div>
                            <h4><i class="mdi mdi-heart text-danger">i>你好,感谢支持<i class="mdi mdi-heart text-danger">i>h4>
                            <h6 class="font-weight-light" id="id-login-info">请输入帐密登录h6>
                            <form class="pt-3" id="id-login-form" autocomplete="off">
                                <div class="form-group">
                                    <input type="text" class="form-control form-control-lg" name="username" placeholder="用户名">
                                div>
                                <div class="form-group">
                                    <input type="password" class="form-control form-control-lg" name="password" placeholder="密码" autocomplete="off">
                                div>
                                <div class="mt-3">
                                    <a class="btn btn-block btn-gradient-primary btn-lg font-weight-medium auth-form-btn" href="javascript:void(0)" id="id-login-button">登录a>
                                div>
                                {% csrf_token %}
                                <div class="my-2 d-flex justify-content-between align-items-center">
                                    <div class="form-check">

                                    div>
                                    <a href="//{{ simpleauth_url }}/simpleauth/modify/?next=//{{ request.get_host }}{{ request.get_full_path }}" class="auth-link text-black">修改密码a>
                                div>

                                <div class="text-center mt-4 font-weight-light">
                                    没有账户? <a href="//{{ simpleauth_url }}/simpleauth/register/?next=//{{ request.get_host }}{{ request.get_full_path }}" class="text-primary">创建a>
                                div>
                            form>
                        div>
                    div>

                    <p style="position:absolute; bottom:0; padding: 0; margin:0; text-align: center;width: 100%">
                        <small>Copyright © 2020 <a href="//{{ simpleauth_url }}/simpleauth/login/">吾星喵认证系统a> All Rights Reserved.
                        small>
                    p>
                div>
            div>
            <div class="modal-footer">
            div>
        div>
    div>
div>

[外链图片转存失败(img-Be8ijwak-1562813971812)(https://blog.starmeow.cn/media/blog/images/2019/07/BLOG_20190711_105256_93.png “博客图集BLOG_20190711_105256_93.png”)]

该模态框限制不可退出。

jquery登录验证,请求另一域名A应用登录

!jquery登录post(一)

点击登录按钮,获取表单,进行ajax提交

$('#id-login-button').click(function () {
    //console.log($("#id-login-form").serialize());

    //$.ajaxSettings.async = false;
    //let token = '';
    //$.get('//{{ simpleauth_url }}/simpleauth/token/', function (data) {
    //    token = data.token;  //simpleauth中获取token,不使用
    //});

    $.ajax({
        url: '//{{ simpleauth_url }}/simpleauth/login/',
        type: 'POST',
        cache: false,
        //dataType: "json",
        //headers: {"X-CSRFToken": $.cookie('csrftoken')},
        data: $("#id-login-form").serialize(),
        async: true,
        beforeSend: function (xhr, settings) {
            //xhr.setRequestHeader("X-CSRFToken", token);
            xhr.setRequestHeader("X-Requested-With", 'XMLHttpRequest'); //指定为ajax访问
        },
        success: function (data) {
            console.log(data);
            if (data.state_value === 1) {
                //设置cookie中的值
                let expiresDate = new Date();
                expiresDate.setTime(expiresDate.getTime() + (30 * 60 * 1000));
                $.cookie('simpleauth_username', data.data.username, {expires: expiresDate, path: '/'});
                $.cookie('simpleauth_nickname', data.data.nickname, {expires: expiresDate, path: '/'});
                $.cookie('simpleauth_token', data.data.token, {expires: expiresDate, path: '/'});
                //填充用户名
                $('#id-login-nickname').html(data.data.nickname);
                //隐藏模态框
                $('#tokenAuthLoginModal').modal('hide');
                setTimeout(function () {//1秒后刷新页面
                    window.location.reload();
                }, 500)
            } else {
                $('#id-login-info').html(data.msg);  //登陆出错,填充提示信息
                setTimeout(function () {//3秒后跳转
                    $('#id-login-info').html('请重新输入');
                }, 3000)
            }
        },
    })
});

A应用:后端接收该表单进行验证

!模型中生成用户Token方法(二)

定义def _generate_jwt_token(self)方法用于生成token

class SimpleUser(models.Model):
    username = models.CharField(max_length=30, verbose_name='用户名')
    nickname = models.CharField(max_length=30, null=True, blank=True, verbose_name='昵称')
    email = models.EmailField(null=True, blank=True, verbose_name='邮箱')
    password = models.CharField(max_length=200, verbose_name='密码')
    group = models.ManyToManyField(SimpleGroup, blank=True, related_name='users', verbose_name='用户组')
    permission = models.ManyToManyField(SimplePermission, blank=True, related_name='users', verbose_name='权限')

    # 生成jwt的token
    def _generate_jwt_token(self):
        import datetime
        import jwt
        from django.conf import settings
        from django.utils import timezone
        payload = {
            'exp': timezone.now() + datetime.timedelta(minutes=3),  # 设置过期时间:https://pyjwt.readthedocs.io/en/latest/usage.html#expiration-time-claim-exp
            'iat': timezone.now(),  # 设置发布时间:https://pyjwt.readthedocs.io/en/latest/usage.html#issued-at-claim-iat
            'data': {'username': self.username, 'nickname': self.nickname}  # 添加自定义的数据,为一个包含用户名和昵称的字典
        }
        secret = settings.SECRET_KEY  # 密钥字符串
        algorithm = 'HS256'  # 指定签名算法
        encoded = jwt.encode(payload, secret, algorithm)  # 生成byte类型的token
        token = encoded.decode('utf-8')  # 转为字符串
        return token

    @property  # 将方法转为属性来使用,直接使用 obj.token 就可以获取值
    def token(self):
        return self._generate_jwt_token()

    class Meta:
        verbose_name_plural = verbose_name = '简单用户'

    def __str__(self):
        return '【{}】 昵称:{}'.format(self.username, self.nickname)

命令行测试生成用户的Token

在model中编写完生成Token的方法后,需要重启Django Console才可进行下面的测试,否则obj.token无法使用

>>> from simpleauth.models import SimpleUser
>>> user = SimpleUser.objects.get(username='admin')

user.token
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjI0NDM5ODcsImlhdCI6MTU2MjQ0MzgwNywiZGF0YSI6eyJ1c2VybmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbkBhZG1pbi5jb20ifX0.Ry5BHjIdCuqhAyC-Wo558q4JQI9Qaf4NgTIm034cumg'

!A应用登录验证帐密(三)

关闭csrf验证

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt


class SimpleUserLoginView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def get(self, request):
        print('用户登录是否是Ajax请求:', request.is_ajax())
        next_url = request.GET.get('next', '')

        user_login_form = SimpleUserLoginForm()
        return render(request, 'simpleauth/auth_v0_2/simple-auth-login.html', locals())

    def post(self, request):
        print('用户登录是否是Ajax请求:', request.is_ajax())
        next_url = request.POST.get('next')  # 获取隐藏表单的next
        username = request.POST.get('username')
        password = request.POST.get('password')

        user_login_form = SimpleUserLoginForm(request.POST)
        print(user_login_form.errors)
        if user_login_form.is_valid():
            user = SimpleUser.objects.get(Q(username=username) | Q(email=username))
            # print(user)
            if check_password(password, user.password):
                # 【ajax请求】帐密校验通过
                if request.is_ajax():
                    # 将model中的user.token传给前端token
                    return JsonResponse({'state_value': 1, 'msg': '登录成功', 'data': {'username': user.username, 'nickname': user.nickname, 'token': user.token}})

                # 如果不是ajax请求,就进入下面判断
                success_info = '用户登录成功。由于没有指定下一跳地址,请重新访问之前的页面'
                # 用户已登录状态,session中有值
                if request.session.get('session_simple_user_name') == username:
                    print('用户已登录,返回:', next_url)
                    if next_url:
                        return redirect(next_url)
                    else:
                        return render(request, 'simpleauth/auth_v0_2/simple-auth-info.html', {'success_info': success_info})
                        # return HttpResponse('

用户登录成功。

由于没有制定下一跳地址,请重新访问之前的页面')
# 用户未登录状态,添加到session else: request.session['session_simple_user_name'] = username request.session['session_simple_nick_name'] = user.nickname if next_url: return redirect(next_url) else: return render(request, 'simpleauth/auth_v0_2/simple-auth-info.html', {'success_info': success_info}) else: msg = '用户名或密码错误' # 【ajax请求】帐密错误时 if request.is_ajax(): return JsonResponse({'state_value': 0, 'msg': msg}) else: messages.add_message(request, messages.INFO, msg) return render(request, 'simpleauth/auth_v0_2/simple-auth-login.html', locals()) else: # 【ajax请求】表单错误时 if request.is_ajax(): msg = '' # print(user_login_form.errors.get_json_data()) # {'username': [{'message': '用户不存在,请注册', 'code': ''}]} try: for key, value in user_login_form.errors.get_json_data().items(): msg += value[0].get('message') except BaseException: msg += '其它错误' return JsonResponse({'state_value': 0, 'msg': msg}) else: return render(request, 'simpleauth/auth_v0_2/simple-auth-login.html', locals())

修改普通的认证方式,当用户以ajax请求时,返回json字符串提供给前端显示。

!A应用跨域解决方案

A应用中要设置跨域白名单,首先安装 pip install django-cors-headers,https://pypi.org/project/django-cors-headers/

修改 settings.py

INSTALLED_APPS = [
    # ...
    'corsheaders',  # 跨域(第1步):添加应用
    # ...
]

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    # ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
]

# 跨域(第3步):配置其他信息
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = (  # 白名单的完整域名
#     "http://testdomain.starmeow.cn",
#     "https://testdomain.starmeow.cn",
# )
CORS_ORIGIN_REGEX_WHITELIST = [  # 白名单的正则字符串,当有多个域名时,非常有用
    r"^http://\w+\.starmeow\.cn",
    r"^https://\w+\.starmeow\.cn",
]

CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)

CORS_ALLOW_HEADERS = (
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

B应用页面jquery获取数据

A应用登录认证装饰器代码判定位置

修改以前的登录装饰器,增加http_authorization = request.META.get('HTTP_AUTHORIZATION')的判断,如果session中没获取到用户名,如果没获取到,尝试从token中获取,代码占位,后面补充。

def simple_login_required(view_func):
    """
    使用方法:
    基于类的视图:@method_decorator(simple_login_required, name='dispatch'),添加到类上方
    基于函数的视图:@simple_login_required,添加到函数的上方
    如果request.is_ajax(),那么返回json字符串,例如:result = {'state_value': 0, 'msg': msg},0表示失败,1表示成功,msg表示提示信息
    :param view_func:
    :return:
    """

    def my_login(request, *args, **kwargs):
        # print(view_func)
        next_url = '{}{}'.format(request._current_scheme_host, request.path)
        # print('登陆后返回链接:', next_url)

        simple_user_name = request.session.get('session_simple_user_name', '')
        print('用户登录:', simple_user_name)
        # 从session中获取session_simple_user_name,能获取到表明是已登录状态
        if simple_user_name:
            try:
                return view_func(request, *args, **kwargs)
            except BaseException as e:  # 用户被删异常:simpleauth.models.SimpleUser.DoesNotExist: SimpleUser matching query does not exist.
                print(e)
                msg = '登录异常,请重新登录'
                if simple_user_name == 'admin':
                    msg += str(e) + '访问/simpleauth/主页生成'

                # 如果是ajax访问,返回json提示,否则跳转到登录页面
                if request.is_ajax():
                    result = {'state_value': 0, 'msg': msg}
                    return JsonResponse(result)
                else:
                    messages.warning(request, msg)
                    return redirect(reverse('simpleauth:simple_user_login') + '?next={}'.format(next_url))
        else:
            # 如果session认证登录失败,尝试进行token认证登录
            http_authorization = request.META.get('HTTP_AUTHORIZATION')
            print(http_authorization)
            if http_authorization:
                # 尝试token认证登录
                pass

            else:
                # 如果token认证失败,最后再返回 未登录 提示
                msg = '用户未登录'
                if request.is_ajax():
                    result = {'state_value': 0, 'msg': msg}
                    return JsonResponse(result)
                else:
                    messages.warning(request, msg)
                    return redirect(reverse('simpleauth:simple_user_login') + '?next={}'.format(next_url))

    return my_login

B应用视图中加上该装饰器验证登录(五)

使用simple_login_required这个装饰器,要求只能在登录的情况下才能访问该视图。

# 根据订单号获取已选中的物品
@simple_login_required
def api_get_buyer_purchased(request):
    username=get_request_username(request)
    # ...其他代码省略

下面来测试通过url访问该视图

命令行测试通过Token请求B应用的URL

通过获取到的token来请求认证

>>> import requests
>>> token = user.token

>>> r = requests.get('http://testdomain.starmeow.cn/api/get/purchased/?order_number=201907032124123963', headers={'Authorization': 'Token ' + token})

在认证的装饰器中,可以通过META中的HTTP_AUTHORIZATION得到下面的数据

# 权限认证装饰器中的部分代码
http_authorization = request.META.get('HTTP_AUTHORIZATION')
print(http_authorization)

可以看到输出为Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjI0NDQxOTMsImlhdCI6MTU2MjQ0NDAxMywiZGF0YSI6eyJ1c2VybmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbkBhZG1pbi5jb20ifX0.cMIevl-9OjgRsU26L45NORk8oPaPG2IlfXNzFgXhZ9Q

如何在Django完成数据的返回?

A应用登录认证装饰器判断返回数据(四)

# 判断是否登录的装饰器

def simple_login_required(view_func):
    """
    使用方法:
    基于类的视图:@method_decorator(simple_login_required, name='dispatch'),添加到类上方
    基于函数的视图:@simple_login_required,添加到函数的上方
    如果request.is_ajax(),那么返回json字符串,例如:result = {'state_value': 0, 'msg': msg},0表示失败,1表示成功,msg表示提示信息
    如果是Token,认证登录,根据抛出的异常,返回不同的json信息
    :param view_func:
    :return:
    """

    def my_login(request, *args, **kwargs):
        # print(view_func)
        next_url = '{}{}'.format(request._current_scheme_host, request.path)
        # print('登陆后返回链接:', next_url)

        simple_user_name = request.session.get('session_simple_user_name', '')
        print('用户登录:{},链接:{},通过ajax请求:{}'.format(simple_user_name, request.path, request.is_ajax()))
        # 【1、从session中获取登录状态】从session中获取session_simple_user_name,能获取到表明是已登录状态
        if simple_user_name:
            try:
                return view_func(request, *args, **kwargs)
            except BaseException as e:  # 用户被删异常:simpleauth.models.SimpleUser.DoesNotExist: SimpleUser matching query does not exist.
                print(e)
                msg = '登录异常,请重新登录'
                if simple_user_name == 'admin':
                    msg += str(e) + '访问/simpleauth/主页生成'

                # 如果是ajax访问,返回json提示,否则跳转到登录页面
                if request.is_ajax():
                    result = {'state_value': 0, 'msg': msg}
                    return JsonResponse(result)
                else:
                    messages.warning(request, msg)
                    return redirect(reverse('simpleauth:simple_user_login') + '?next={}'.format(next_url))
        else:
            # 如果session认证登录失败,尝试进行token认证登录
            http_authorization = request.META.get('HTTP_AUTHORIZATION')
            print('获取HTTP_AUTHORIZATION:', http_authorization)  # 如果是Token认证,就是:Token code???????code
            if http_authorization:
                # 尝试token认证登录
                method, code = http_authorization.split(' ')  # 空格分割字符串
                if method.lower() == 'token':
                    # 【2、从Token判断登录状态】解析Token判断用户
                    import jwt
                    from django.conf import settings
                    try:
                        # 尝试进行Token(令牌)解密
                        decoded = jwt.decode(code, settings.SECRET_KEY, algorithm='HS256')
                        username = decoded.get('data').get('username')  # 获取用户名:model中生成Token时放在payload的data中的数据
                        print('Token中获取用户名:', username)
                        nickname = decoded.get('data').get('nickname')
                    except jwt.DecodeError:  # 可以在pyjwt源码中看有哪些错误
                        return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '解码错误,请退出后重新登陆'})
                    except jwt.ExpiredSignatureError:
                        return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '签名过期,请退出后重新登陆'})
                    except jwt.InvalidTokenError:
                        return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '令牌无效,请退出后重新登陆'})
                    except Exception:
                        return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '其它错误,请退出后重新登陆'})

                    # 根据查询到的username从用户数据库中查询
                    try:
                        simple_user = SimpleUser.objects.get(username=username)
                    except SimpleUser.DoesNotExist:
                        return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '用户不存在,请检查用户名或重新注册'})

                    # 最后验证成功后返回原函数
                    return view_func(request, *args, **kwargs)

                else:
                    # 不是token认证
                    return JsonResponse({'state_value': 0, 'status_code': 401, 'msg': '暂不支持的认证类型'})

            else:
                # 【3、session和Token均认证失败】如果token认证失败,最后再返回 未登录 提示
                msg = '用户未登录'
                if request.is_ajax():
                    result = {'state_value': 0, 'msg': msg}
                    return JsonResponse(result)
                else:
                    messages.warning(request, msg)
                    # return redirect(reverse('simpleauth:simple_user_login') + '?next={}'.format(next_url))
                    return redirect('http://127.0.0.1/simpleauth/login/?next={}'.format(next_url))

    return my_login

通过这个装饰器验证是否已登录,如果是,就返回处理该API的请求结果

命令行通过Token请求返回

在装饰器中完成数据返回后,重新测试

>>> r = requests.get('http://testdomain.starmeow.cn/api/get/purchased/?order_number=201907032124123963', headers={'Authorization': 'Token ' + token})
# 如果返回有错误的,可以通过json来查看里面的值
>>> t.json()

能够正常验证后就返回以前的逻辑return view_func(request, *args, **kwargs)

!jquery全局带token请求(六)

//JQuery ajax全局配置
$.ajaxSetup({
    headers: {
        "Authorization": "Token " + $.cookie('simpleauth_token')
    }
});

//输入框输入后点击提交
function update_selected_nums(update_id) {
    let new_nums = $('#new_nums_{update_id}'.replace(/{update_id}/, update_id)).val();
    if (isNaN(parseFloat(new_nums)) || parseFloat(new_nums) <= 0) {
        alert('数值错误,请重新输入!');
        return;
    }
    $.post("{% host_url 'update_selected_works' host 'group_buy_v0_3' %}",
        {"order_number": order_number, 'works_id': update_id, 'new_nums': new_nums, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
        function (data) {
            if (data.state_value === 0) { //登录装饰器返回
                alert(data.msg);
                return;
            }
            if (data.status !== 1) {
                //alert(data.result);
                console.log(data.result);
            } else {
                console.log(data.result);
            }
            get_selected_and_optional();  // 调用页面更新函数
        }
    );
}

!退出登录删除cookie

删除对应的cookie值后,显示登录模态框

$('#id-logout-button').click(function () {
    //退出登录,置为空,切记要指定路径
    $.cookie('simpleauth_username', '', {path: '/'});
    $.cookie('simpleauth_nickname', '', {path: '/'});
    $.cookie('simpleauth_token', '', {path: '/'});
    show_login_modal(); // 退出登录后显示模态框
})

你可能感兴趣的:(Web开发)