From c323519dd4538463109e4b588e0f727fb269575a Mon Sep 17 00:00:00 2001 From: zhenchaozhu Date: Tue, 29 Nov 2016 19:17:57 +0800 Subject: [PATCH] m --- decrators.py | 111 ++++++++++++++++++++++++++++++++++++ forum/forms/user.py | 33 +++++++++-- forum/views/user.py | 14 +++-- middlewares/__init__.py | 0 middlewares/session_middleware.py | 115 ++++++++++++++++++++++++++++++++++++++ xp/settings.py | 5 +- 6 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 decrators.py create mode 100644 middlewares/__init__.py create mode 100644 middlewares/session_middleware.py diff --git a/decrators.py b/decrators.py new file mode 100644 index 0000000..268bbd2 --- /dev/null +++ b/decrators.py @@ -0,0 +1,111 @@ +# coding: utf-8 + +import django.http +from functools import wraps +from django.conf import settings +from django.utils.decorators import available_attrs +from django.shortcuts import resolve_url +from django.utils.six.moves.urllib.parse import urlparse + +def weapp_django_logout(request, response): + assert isinstance(request, django.http.HttpRequest) + assert isinstance(response, django.http.HttpResponse) + # 请求统一认证登出 + response.delete_cookie('pt') + response.delete_cookie('pu') + response.delete_cookie('username') + return response + +def weapp_logout(func=weapp_django_logout): + """ + Django登出视图函数装饰器 + :param func: 处理登出逻辑的函数,接受request和response两个参数 + :return: + """ + return auth_decorator_factory(func) + + +def auth_decorator_factory(func=None): + """ + 登录登出装饰器工厂函数,允许用户自定义外部登录登出函数 + :param func: + :return: + """ + + def decorator(view_func): + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + response = view_func(request, *args, **kwargs) + if func: + func(request, response) + return response + + return _wrapped_view + + return decorator + +import json + +from django.http import JsonResponse + + +class QCCRJsonResponse(JsonResponse): + """ + 封装了返回码和信息的JsonResponse类 + """ + def __init__(self, obj=None, code=200, msg="success", status=200, **kwargs): + data = {"code": code, "msg": msg, "result": json.loads(obj) if isinstance(obj, (str, unicode)) else obj} + super(QCCRJsonResponse, self).__init__(data=data, status=status, **kwargs) + + +class QCCRErrorResponse(QCCRJsonResponse): + """ + 请求错误 + """ + def __init__(self, obj=None, code=400, msg="failure", status=400, **kwargs): + super(QCCRErrorResponse, self).__init__(obj=obj, code=code, msg=msg, status=status, **kwargs) + +def user_passes_test(test_func, login_url=None, + redirect_field_name=getattr(settings, "QCCR_REDIRECT_FIELD_NAME", 'redirect')): + """ + 本装饰器通过test_func函数,检查views是否通过相应的检测,未通过则跳转到登录页面;test_func函数接受user对象作为参数,通过则返回True + """ + + def decorator(view_func): + @wraps(view_func, assigned=available_attrs(view_func)) + def _wrapped_view(request, *args, **kwargs): + if test_func(request.user): + return view_func(request, *args, **kwargs) + # 如果未通过检查,且为ajax请求,返回403 + if request.is_ajax(): + return QCCRErrorResponse(code=403, msg="用户身份检查失败", status=403) + # 非ajax请求,跳转到登录页面 + path = request.build_absolute_uri() + resolved_login_url = resolve_url(login_url or getattr(settings, "QCCR_LOGIN_URL", '/login/')) + login_scheme, login_netloc = urlparse(resolved_login_url)[:2] + current_scheme, current_netloc = urlparse(path)[:2] + if ((not login_scheme or login_scheme == current_scheme) and + (not login_netloc or login_netloc == current_netloc)): + path = request.get_full_path() + from django.contrib.auth.views import redirect_to_login + return redirect_to_login(path, resolved_login_url, redirect_field_name) + + return _wrapped_view + + return decorator + + +def weapp_login_required(function=None, + redirect_field_name=getattr(settings, "QCCR_REDIRECT_FIELD_NAME", 'redirect'), + login_url=None): + """ + 登录状态检查装饰器,如果未登录,则直接跳转到登录页面 + """ + actual_decorator = user_passes_test( + lambda u: u.is_authenticated(), + login_url=login_url, + redirect_field_name=redirect_field_name + ) + if function: + return actual_decorator(function) + return actual_decorator \ No newline at end of file diff --git a/forum/forms/user.py b/forum/forms/user.py index 1c6838d..84b322e 100644 --- a/forum/forms/user.py +++ b/forum/forms/user.py @@ -1,5 +1,6 @@ # coding: utf-8 +import requests from django import forms from django.contrib.auth import authenticate from django.conf import settings @@ -85,6 +86,9 @@ class LoginForm(forms.Form): def __init__(self, *args, **kwargs): self.user_cache = None + self.pu = '' + self.pt = '' + self.username = '' super(LoginForm, self).__init__(*args, **kwargs) def clean(self): @@ -92,15 +96,32 @@ class LoginForm(forms.Form): password = self.cleaned_data.get('password') if username and password: - self.user_cache = authenticate(username=username, password=password) - if self.user_cache is None: - raise forms.ValidationError(u'用户名或者密码不正确') - elif not self.user_cache.is_active: - raise forms.ValidationError(u'用户已被锁定,请联系管理员解锁') + post_params = { + 'comefrom': 2, + 'user_name': username, + 'password': password, + } + resp = requests.post(settings.AUTH_DOMAIN, data=post_params, verify=False) + if resp.status_code == 200: + rst = resp.json() + if rst.get('status') == 1: + data = rst.get('data') + self.pt = data.get('token') + self.pu = data.get('suid') + self.username = username + else: + raise forms.ValidationError(u'用户名或者密码不正确') + else: + raise forms.ValidationError(u'连接登录中心失败') + return self.cleaned_data def get_user(self): - return self.user_cache + return { + 'pt': self.pt, + 'pu': self.pu, + 'username': self.username, + } class RegisterForm(forms.ModelForm): diff --git a/forum/views/user.py b/forum/views/user.py index 8405b1d..544f9f5 100644 --- a/forum/views/user.py +++ b/forum/views/user.py @@ -1,5 +1,6 @@ # coding: utf-8 +import requests import os, uuid, copy, urllib from PIL import Image from django.shortcuts import render_to_response, redirect @@ -128,13 +129,14 @@ def post_login(request): if not form.is_valid(): return get_login(request, errors=form.errors) - user = form.get_user() - auth.login(request, user) - - if user.is_staff: - return redirect(request.REQUEST.get('next', '/manage/admin/')) + user_info = form.get_user() + pt = user_info.get('pt') + pu = user_info.get('pu') + t = redirect(request.REQUEST.get('next', '/')) + t.set_cookie('pt', pt, 864000) + t.set_cookie('pu', pu, 864000) - return redirect(request.REQUEST.get('next', '/')) + return t def get_logout(request): diff --git a/middlewares/__init__.py b/middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/middlewares/session_middleware.py b/middlewares/session_middleware.py new file mode 100644 index 0000000..3b6d4ae --- /dev/null +++ b/middlewares/session_middleware.py @@ -0,0 +1,115 @@ +# coding: utf-8 + +import requests +from django.conf import settings +from django.core.cache import caches +from django.contrib.auth import get_user_model +from django.contrib.auth.models import AnonymousUser + + +class SessionWithoutLocalUserMiddleware(object): + """ + 统一权限(认证)中间件,Django系统本地不保存用户的情况使用 + """ + + def __init__(self): + self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS + self.cache = caches[self.cache_alias] + self.UserModel = get_user_model() + + def process_request(self, request): + if hasattr(request, "user") and getattr(request.user, "is_superuser", False): + # 对于Django系统的admin用户,这里不做任何处理 + pass + else: + pt = request.COOKIES.get('pt') + pu = request.COOKIES.get('pu') + username = request.COOKIES.get('username') + if pt and pu: + # 查询session状态成功的情况,构造QCCRUser + user = XYTUser(username, pu, pt) + request.user = user + else: + # 拿不到统一认证的session,将当前用户设为匿名用户 + request.user = AnonymousUser() + + +class Manager(object): + + def __init__(self): + self.auth_domain = 'https://api.xiuyetang.com/sys/user/login' + + +class XYTUser(object): + id = None + pk = None + username = '' + sessionId = '' + accountNo = '' + employeeName = '' + employeeId = 0 + employeeNo = '' + employeeTel = '' + deptIds = '' + email = '' + entryTime = '' + uid = '' + is_staff = False + is_active = False + is_superuser = False + _groups = '' + _user_permissions = '' + + def __init__(self, username, pu, pt): + self.username = username + self.id = pu + self.pk = pu + self.sessionId = pt + + def __str__(self): + return self.username + + def __eq__(self, other): + return self.username == other.username + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self.username) + + def save(self): + raise NotImplementedError("Django doesn't provide a DB representation for QCCRUser. User info in LDAP.") + + def delete(self): + raise NotImplementedError("Django doesn't provide a DB representation for QCCRUser. User info in LDAP.") + + def set_password(self, raw_password): + raise NotImplementedError("Django doesn't provide a DB representation for QCCRUser. Password in LDAP.") + + def check_password(self, raw_password): + raise NotImplementedError("Django doesn't provide a DB representation for QCCRUser. Password in LDAP.") + + def _get_groups(self): + return self._groups + + groups = property(_get_groups) + + def _get_user_permissions(self): + return self._user_permissions + + user_permissions = property(_get_user_permissions) + + def get_group_permissions(self, obj=None): + return set() + + @property + def is_anonymous(self): + return lambda: False + + @property + def is_authenticated(self): + return lambda: True + + def get_username(self): + return self.username \ No newline at end of file diff --git a/xp/settings.py b/xp/settings.py index 9992357..90a0a0d 100644 --- a/xp/settings.py +++ b/xp/settings.py @@ -16,7 +16,7 @@ DATABASES = { 'NAME': 'forum', # Or path to database file if using sqlite3. # The following settings are not used with sqlite3: 'USER': 'root', - 'PASSWORD': 'nineteen', + 'PASSWORD': 'zzc', 'HOST': '127.0.0.1', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 'PORT': '3306', # Set to empty string for default. } @@ -183,12 +183,13 @@ LOGGING = { # } # SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 使用memcached存储session +AUTH_DOMAIN = 'https://api.xiuyetang.com/service/user/login' # 自定义User类 AUTH_USER_MODEL = 'forum.ForumUser' # 用户认证BackEnds -# AUTHENTICATION_BACKENDS = ('forum.backends.EmailAuthBackend',) +AUTHENTICATION_BACKENDS = ('forum.backends.EmailAuthBackend',) # 默认登陆uri LOGIN_URL = '/login/' -- 2.0.0