diff --git a/3rdlib/jwt_auth/.gitignore b/3rdlib/jwt_auth/.gitignore new file mode 100644 index 0000000..e0d25df --- /dev/null +++ b/3rdlib/jwt_auth/.gitignore @@ -0,0 +1,8 @@ +# Compiled python modules. +*.pyc + +# Setuptools distribution folder. +/dist/ + +# Python egg metadata, regenerated from source files by setuptools. +/*.egg-info diff --git a/3rdlib/jwt_auth/MANIFEST.in b/3rdlib/jwt_auth/MANIFEST.in new file mode 100644 index 0000000..9561fb1 --- /dev/null +++ b/3rdlib/jwt_auth/MANIFEST.in @@ -0,0 +1 @@ +include README.rst diff --git a/3rdlib/jwt_auth/README.rst b/3rdlib/jwt_auth/README.rst new file mode 100644 index 0000000..4942026 --- /dev/null +++ b/3rdlib/jwt_auth/README.rst @@ -0,0 +1,6 @@ +jwt_auth +-------- + +To use (with caution), simply do:: +jwt_auth +API \ No newline at end of file diff --git a/3rdlib/jwt_auth/jwt_auth/__init__.py b/3rdlib/jwt_auth/jwt_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/3rdlib/jwt_auth/jwt_auth/admin.py b/3rdlib/jwt_auth/jwt_auth/admin.py new file mode 100644 index 0000000..5285516 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/admin.py @@ -0,0 +1,71 @@ +# coding: utf-8 + + +from django import forms +from django.contrib import admin +from django.contrib.auth.models import Group +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField + +from .models import Staff + + +class StaffCreationForm(forms.ModelForm): + password1 = forms.CharField(widget=forms.PasswordInput) + password2 = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = Staff + fields = ('email', 'realname') + + def clean_password2(self): + password1 = self.cleaned_data.get('password1') + password2 = self.cleaned_data.get('password2') + if password1 and password2 and password1 != password2: + raise forms.ValidationError('密码不正确') + return password2 + + def save(self, commit=True): + staff = super(StaffCreationForm, self).save(commit=False) + staff.set_password(self.cleaned_data['password1']) + if commit: + staff.save() + return staff + + +class StaffChangeForm(forms.ModelForm): + password = ReadOnlyPasswordHashField() + + class Meta: + model = Staff + fields = ('email', 'password', 'realname') + + def clean_password(self): + return self.initial['password'] + + +class StaffAdmin(BaseUserAdmin): + form = StaffChangeForm + add_form = StaffCreationForm + + list_display = ('email', 'is_admin') + list_filter = ('is_admin',) + fieldsets = ( + (None, {'fields': ('email', 'realname', 'password')}), + ('Personal info', {'fields': ('last_login',)}), + ('Permissions', {'fields': ('is_admin',)}), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'last_login', 'password1', 'password2')} + ), + ) + search_fields = ('email',) + ordering = ('email',) + filter_horizontal = () + + +# Now register the new UserAdmin +admin.site.register(Staff, StaffAdmin) +admin.site.unregister(Group) diff --git a/3rdlib/jwt_auth/jwt_auth/apps.py b/3rdlib/jwt_auth/jwt_auth/apps.py new file mode 100644 index 0000000..904b0a9 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class JwtAuthConfig(AppConfig): + name = 'jwt_auth' diff --git a/3rdlib/jwt_auth/jwt_auth/backends.py b/3rdlib/jwt_auth/jwt_auth/backends.py new file mode 100644 index 0000000..c804606 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/backends.py @@ -0,0 +1,115 @@ +import jwt +from django.conf import settings +from rest_framework import authentication, exceptions +from .models import Staff + + +class JWTAuthentication(authentication.BaseAuthentication): + authentication_header_prefix = 'Token' + + def authenticate(self, request): + """ + The `authenticate` method is called on every request regardless of + whether the endpoint requires authentication. + + `authenticate` has two possible return values: + + 1) `None` - We return `None` if we do not wish to authenticate. Usually + this means we know authentication will fail. An example of + this is when the request does not include a token in the + headers. + + 2) `(user, token)` - We return a user/token combination when + authentication is successful. + + If neither case is met, that means there's an error + and we do not return anything. + We simple raise the `AuthenticationFailed` + exception and let Django REST Framework + handle the rest. + """ + request.user = None + + # `auth_header` should be an array with two elements: 1) the name of + # the authentication header (in this case, "Token") and 2) the JWT + # that we should authenticate against. + auth_header = authentication.get_authorization_header(request).split() + auth_header_prefix = self.authentication_header_prefix.lower() + + if not auth_header: + return None + + if len(auth_header) == 1: + # Invalid token header. No credentials provided. Do not attempt to + # authenticate. + return None + + elif len(auth_header) > 2: + # Invalid token header. The Token string should not contain spaces. Do + # not attempt to authenticate. + return None + + # The JWT library we're using can't handle the `byte` type, which is + # commonly used by standard libraries in Python 3. To get around this, + # we simply have to decode `prefix` and `token`. This does not make for + # clean code, but it is a good decision because we would get an error + # if we didn't decode these values. + prefix = auth_header[0].decode('utf-8') + token = auth_header[1].decode('utf-8') + + if prefix.lower() != auth_header_prefix: + # The auth header prefix is not what we expected. Do not attempt to + # authenticate. + return None + + # By now, we are sure there is a *chance* that authentication will + # succeed. We delegate the actual credentials authentication to the + # method below. + return self._authenticate_credentials(request, token) + + def _authenticate_credentials(self, request, token): + """ + Try to authenticate the given credentials. If authentication is + successful, return the user and token. If not, throw an error. + """ + try: + payload = jwt.decode(token, settings.SECRET_KEY) + except: + msg = 'Invalid authentication. Could not decode token.' + raise exceptions.AuthenticationFailed(msg) + + try: + staff = Staff.objects.get(pk=payload['id']) + except Staff.DoesNotExist: + msg = 'No staff matching this token was found.' + raise exceptions.AuthenticationFailed(msg) + + if not staff.is_active: + msg = 'This user has been deactivated.' + raise exceptions.AuthenticationFailed(msg) + + return (staff, token) + + +class EmailOrMobileAuthBackend(object): + def authenticate(self, email=None, password=None): + try: + user = Staff.objects.get(email=email) + if user.check_password(password): + return user + except Staff.DoesNotExist: + if email.isdigit(): + try: + user = Staff.objects.get(mobile=email) + if user.check_password(password): + return user + except Staff.DoesNotExist: + return None + else: + return None + + def get_user(self, user_id): + try: + return Staff.objects.get(pk=user_id) + except Staff.DoesNotExist: + return None \ No newline at end of file diff --git a/3rdlib/jwt_auth/jwt_auth/migrations/0001_initial.py b/3rdlib/jwt_auth/jwt_auth/migrations/0001_initial.py new file mode 100644 index 0000000..6d326e0 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2017-12-13 07:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Staff', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('email', models.EmailField(max_length=255, primary_key=True, serialize=False, unique=True)), + ('is_active', models.BooleanField(default=True)), + ('is_admin', models.BooleanField(default=False)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/3rdlib/jwt_auth/jwt_auth/migrations/0002_staff_username.py b/3rdlib/jwt_auth/jwt_auth/migrations/0002_staff_username.py new file mode 100644 index 0000000..18b1b4d --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/migrations/0002_staff_username.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2017-12-13 08:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jwt_auth', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='staff', + name='username', + field=models.CharField(default='', max_length=24, verbose_name='用户名'), + ), + ] diff --git a/3rdlib/jwt_auth/jwt_auth/migrations/0003_auto_20171213_0850.py b/3rdlib/jwt_auth/jwt_auth/migrations/0003_auto_20171213_0850.py new file mode 100644 index 0000000..95f120f --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/migrations/0003_auto_20171213_0850.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2017-12-13 08:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('jwt_auth', '0002_staff_username'), + ] + + operations = [ + migrations.RenameField( + model_name='staff', + old_name='username', + new_name='realname', + ), + ] diff --git a/3rdlib/jwt_auth/jwt_auth/migrations/__init__.py b/3rdlib/jwt_auth/jwt_auth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/3rdlib/jwt_auth/jwt_auth/models.py b/3rdlib/jwt_auth/jwt_auth/models.py new file mode 100644 index 0000000..f5e9392 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/models.py @@ -0,0 +1,70 @@ +# coding: utf-8 +from django.db import models +from django.contrib.auth.models import BaseUserManager, AbstractBaseUser +from django.utils import timezone + +class StaffManager(BaseUserManager): + + def create_staff(self, email, password, **kwargs): + if not email: + raise '邮箱地址不能为空' + if not password: + raise ValueError('密码不合法.') + + if not password == kwargs.get('confirm_password'): + raise ValueError('请确认你的密码.') + + staff = self.model(email=self.normalize_email(email), last_login=timezone.now()) + staff.set_password(password) + staff.save(using=self._db) + return staff + + + def create_superuser(self, email, password): + staff = self.create_staff(email, password) + staff.is_admin = True + staff.save(using=self._db) + return staff + + +class Staff(AbstractBaseUser): + realname = models.CharField(u"用户名", max_length=24, default="") + email = models.EmailField(primary_key=True, max_length=255, unique=True) + is_active = models.BooleanField(default=True) + is_admin = models.BooleanField(default=False) + objects = StaffManager() + + USERNAME_FIELD = 'email' + + def is_staff(self): + return self.is_admin + + def __str__(self): + return self.email + + + def has_module_perms(self, demo): + return True + + + def has_perm(self, perm, obj=None): + return True + + + def get_short_name(self): + # The user is identified by their email address + return self.email + + + def get_full_name(self): + # The user is identified by their email address + return self.email + + @property + def is_staff(self): + "Is the user a member of staff?" + # Simplest possible answer: All admins are staff + return self.is_admin + + def get_username(self): + return self.email diff --git a/3rdlib/jwt_auth/jwt_auth/serializers.py b/3rdlib/jwt_auth/jwt_auth/serializers.py new file mode 100644 index 0000000..1437d58 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/serializers.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- + +from rest_framework import serializers +from .models import Staff +from django.utils import timezone +import jwt +import pytz + +from calendar import timegm +from datetime import datetime, timedelta +from django.contrib.auth import authenticate +from django.utils.translation import ugettext as _ +from rest_framework import serializers +from rest_framework_jwt.settings import api_settings + +from django.contrib.auth.backends import ModelBackend + +class PasswordField(serializers.CharField): + + def __init__(self, *args, **kwargs): + if 'style' not in kwargs: + kwargs['style'] = {'input_type': 'password'} + else: + kwargs['style']['input_type'] = 'password' + super(PasswordField, self).__init__(*args, **kwargs) + + +def get_username_field(): + return 'email' + + +jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER +jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER +jwt_decode_handler = api_settings.JWT_DECODE_HANDLER +jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER + + +def jwt_response_payload_handler(token, staff=None, request=None): + staff.last_login = timezone.localtime(timezone.now()) + +# import pdb;pdb.set_trace() + staff.save(update_fields=['last_login']) + + return { + 'token': token, + 'staff': StaffSerializer(staff, context={'request': request}).data + } + +class StaffSerializer(serializers.ModelSerializer): + + # date_joined = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", + # default_timezone=pytz.UTC, read_only=True) + last_login = serializers.DateTimeField(format=_("%Y-%m-%d %H:%M:%S"), + default_timezone=pytz.timezone('Asia/Shanghai'), read_only=True) + + class Meta: + model = Staff + fields = ('email', 'last_login', 'is_active', 'password') + extra_kwargs = {'password': {'write_only': True}} + read_only_fields = ('created_at', 'updated_at', 'is_admin', 'last_login') + + def create(self, validated_data): + staff = Staff(email=validated_data['email']) + staff.set_password(validated_data['password']) + staff.save() + return staff + + + +class JSONWebTokenSerializer(serializers.Serializer): + """ + Serializer class used to validate a username and password. + 'username' is identified by the custom UserModel.USERNAME_FIELD. + Returns a JSON Web Token that can be used to authenticate later calls. + """ + def __init__(self, *args, **kwargs): + """ + Dynamically add the USERNAME_FIELD to self.fields. + """ + super(JSONWebTokenSerializer, self).__init__(*args, **kwargs) + + self.fields[self.username_field] = serializers.CharField() + self.fields['password'] = PasswordField(write_only=True) + + @property + def object(self): + return self.validated_data + + @property + def username_field(self): + return get_username_field() + + def authenticate(self, username=None, password=None, **kwargs): + try: + user = Staff.objects.get(email=username) + if user.check_password(password): + return user + except Exception as e: + return None + + def validate(self, attrs): + credentials = { + self.username_field: attrs.get(self.username_field), + 'password': attrs.get('password') + } + + if all(credentials.values()): + user = self.authenticate(attrs.get(self.username_field), attrs.get('password')) + + if user: + if not user.is_active: + msg = _('User account is disabled.') + raise serializers.ValidationError(msg) + + payload = jwt_payload_handler(user) + + return { + 'token': jwt_encode_handler(payload), + 'user': user + } + else: + msg = _('Unable to log in with provided credentials.') + raise serializers.ValidationError(msg) + else: + msg = _('Must include "{username_field}" and "password".') + msg = msg.format(username_field=self.username_field) + raise serializers.ValidationError(msg) + + + +class VerificationBaseSerializer(serializers.Serializer): + """ + Abstract serializer used for verifying and refreshing JWTs. + """ + token = serializers.CharField() + + def validate(self, attrs): + msg = 'Please define a validate method.' + raise NotImplementedError(msg) + + def _check_payload(self, token): + # Check payload valid (based off of JSONWebTokenAuthentication, + # may want to refactor) + try: + payload = jwt_decode_handler(token) + except jwt.ExpiredSignature: + msg = _('Signature has expired.') + raise serializers.ValidationError(msg) + except jwt.DecodeError: + msg = _('Error decoding signature.') + raise serializers.ValidationError(msg) + + return payload + + def _check_user(self, payload): + username = jwt_get_username_from_payload(payload) + + if not username: + msg = _('Invalid payload.') + raise serializers.ValidationError(msg) + + # Make sure user exists + try: + user = Staff.objects.get_by_natural_key(username) + except Staff.DoesNotExist: + msg = _("User doesn't exist.") + raise serializers.ValidationError(msg) + + if not user.is_active: + msg = _('User account is disabled.') + raise serializers.ValidationError(msg) + + return user + +class RefreshJSONWebTokenSerializer(VerificationBaseSerializer): + """ + Refresh an access token. + """ + @property + def object(self): + return self.validated_data + + def validate(self, attrs): + + token = attrs['token'] + + payload = self._check_payload(token=token) + user = self._check_user(payload=payload) + + if api_settings.JWT_ALLOW_REFRESH: + payload['orig_iat'] = timegm( + datetime.utcnow().utctimetuple() + ) + + # Get and check 'orig_iat' + orig_iat = payload.get('orig_iat') + + if orig_iat: + # Verify expiration + refresh_limit = api_settings.JWT_REFRESH_EXPIRATION_DELTA + + if isinstance(refresh_limit, timedelta): + refresh_limit = (refresh_limit.days * 24 * 3600 + + refresh_limit.seconds) + + expiration_timestamp = orig_iat + int(refresh_limit) + now_timestamp = timegm(datetime.utcnow().utctimetuple()) + + if now_timestamp > expiration_timestamp: + msg = _('Refresh has expired.') + raise serializers.ValidationError(msg) + else: + msg = _('orig_iat field is required.') + raise serializers.ValidationError(msg) + + new_payload = jwt_payload_handler(user) + new_payload['orig_iat'] = orig_iat + + return { + 'token': jwt_encode_handler(new_payload), + 'user': user + } diff --git a/3rdlib/jwt_auth/jwt_auth/tests.py b/3rdlib/jwt_auth/jwt_auth/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/3rdlib/jwt_auth/jwt_auth/urls.py b/3rdlib/jwt_auth/jwt_auth/urls.py new file mode 100644 index 0000000..e53ff67 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/urls.py @@ -0,0 +1,20 @@ + +# coding: utf-8 + +from django.conf.urls import url, include +from django.contrib import admin +from .views import StaffViewSet, CreateStaffView +from rest_framework.routers import DefaultRouter +from .views import obtain_jwt_token, refresh_jwt_token, register_user + + +router = DefaultRouter() +router.register(r'staff', StaffViewSet) + +urlpatterns = [ + + url(r'^api-auth/', obtain_jwt_token), + url(r'^auth-token-refresh/$', refresh_jwt_token), + url(r'^api-register/', register_user), + url(r'^', include(router.urls)), +] diff --git a/3rdlib/jwt_auth/jwt_auth/views.py b/3rdlib/jwt_auth/jwt_auth/views.py new file mode 100644 index 0000000..cdb5eb3 --- /dev/null +++ b/3rdlib/jwt_auth/jwt_auth/views.py @@ -0,0 +1,47 @@ +from rest_framework import viewsets, permissions, mixins, generics +from .models import Staff +from .serializers import StaffSerializer, JSONWebTokenSerializer, RefreshJSONWebTokenSerializer +from rest_framework_jwt.views import JSONWebTokenAPIView + + +class CreateStaffView(mixins.CreateModelMixin, generics.GenericAPIView): + queryset = Staff.objects.all() + permission_classes = (permissions.AllowAny, ) + serializer_class = StaffSerializer + + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + +class StaffViewSet(viewsets.ModelViewSet): + queryset = Staff.objects.all() + serializer_class = StaffSerializer + permission_classes = (permissions.IsAuthenticated, ) + + +class ObtainJSONWebToken(JSONWebTokenAPIView): + """ + API View that receives a POST with a user's username and password. + Returns a JSON Web Token that can be used for authenticated requests. + """ + serializer_class = JSONWebTokenSerializer + + +class RefreshJSONWebToken(JSONWebTokenAPIView): + """ + API View that returns a refreshed token (with new expiration) based on + existing token + If 'orig_iat' field (original issued-at-time) is found, will first check + if it's within expiration window, then copy it to the new token + """ + serializer_class = RefreshJSONWebTokenSerializer + + +register_user = CreateStaffView.as_view() +obtain_jwt_token = ObtainJSONWebToken.as_view() +refresh_jwt_token = RefreshJSONWebToken.as_view() + + + + + diff --git a/3rdlib/jwt_auth/setup.py b/3rdlib/jwt_auth/setup.py new file mode 100644 index 0000000..4976248 --- /dev/null +++ b/3rdlib/jwt_auth/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup + +setup(name='jwt_auth', + version='0.1', + description='The JWT AUTH API', + url='http://github.com/CoinLQ/jwt_auth', + author='Xiandian', + author_email='xiandian1@gmail.com', + license='MIT', + packages=['jwt_auth'], + install_requires=[ + 'djangorestframework-jwt==1.11.0', + 'djangorestframework==3.6.3' + ], + zip_safe=False) \ No newline at end of file diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000..1b4cfa8 Binary files /dev/null and b/dump.rdb differ diff --git a/lqcharacter/settings.py b/lqcharacter/settings.py index a2f414a..344920c 100644 --- a/lqcharacter/settings.py +++ b/lqcharacter/settings.py @@ -12,6 +12,7 @@ """ import os +import datetime from django.core.urlresolvers import reverse_lazy # mysql 数据库 @@ -68,6 +69,7 @@ 'storages', 'celery', 'django_celery_beat', + 'jwt_auth', ] @@ -136,7 +138,7 @@ # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { - 'default1': { + 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, @@ -150,15 +152,15 @@ # 'OPTIONS': {'charset': 'utf8mb4', 'init_command': 'SET default_storage_engine=InnoDB'} # }, # XianDian - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'lqcharacter', - 'USER': 'test', - 'PASSWORD': 'password', - 'HOST': 'localhost', - 'PORT': '3306', - 'OPTIONS': {'charset': 'utf8mb4', 'init_command': 'SET default_storage_engine=InnoDB'} - } + # 'default': { + # 'ENGINE': 'django.db.backends.mysql', + # 'NAME': 'lqcharacter', + # 'USER': 'test', + # 'PASSWORD': 'password', + # 'HOST': 'localhost', + # 'PORT': '3306', + # 'OPTIONS': {'charset': 'utf8mb4', 'init_command': 'SET default_storage_engine=InnoDB'} + # } } @@ -240,7 +242,7 @@ REST_FRAMEWORK = { 'UNICODE_JSON': False, "DEFAULT_RENDERER_CLASSES": ( - "rest_framework.renderers.BrowsableAPIRenderer", + #"rest_framework.renderers.BrowsableAPIRenderer", "rest_framework.renderers.JSONRenderer", ), "DEFAULT_PARSER_CLASSES": ( @@ -252,7 +254,9 @@ ), "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework.authentication.SessionAuthentication", + 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + 'jwt_auth.backends.EmailOrMobileAuthBackend', + 'jwt_auth.backends.JWTAuthentication', ), "DEFAULT_PAGINATION_CLASS": "core.pagination.StandardPagination", "DEFAULT_FILTER_BACKENDS": ( @@ -354,3 +358,11 @@ LOGOUT_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/' + +JWT_AUTH = { + 'JWT_RESPONSE_PAYLOAD_HANDLER': 'jwt_auth.serializers.jwt_response_payload_handler', + 'JWT_ALLOW_REFRESH': True, + 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30), + 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=365), +} + diff --git a/lqcharacter/urls.py b/lqcharacter/urls.py index d71a004..9a41130 100644 --- a/lqcharacter/urls.py +++ b/lqcharacter/urls.py @@ -19,6 +19,7 @@ from django.views.static import serve # 处理静态文件 import xadmin + # xadmin.autodiscover() # version模块自动注册需要版本控制的 Model @@ -35,6 +36,7 @@ # url(r'^accounts/', include('registration.backends.simple.urls')), url(r'^accounts/', include('accounts.urls')), url(r'^files/', include('db_file_storage.urls')), + url(r'^auth/', include("jwt_auth.urls", namespace="registration")), ] @@ -60,8 +62,9 @@ def page_error(request): if settings.DEBUG: # debug_toolbar 插件配置 - import debug_toolbar - urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) + #import debug_toolbar + #urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) + pass else: # 项目部署上线时使用 from lqcharacter.settings import STATIC_ROOT diff --git a/requirements.txt b/requirements.txt index 141f81f..f5dc510 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==1.11.5 +Django==1.11.8 django-crispy-forms==1.6.1 django-formtools==2.0 django-reversion==2.0.10 diff --git a/split_auth/__init__.py b/split_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/split_auth/settings.py b/split_auth/settings.py new file mode 100644 index 0000000..5341a31 --- /dev/null +++ b/split_auth/settings.py @@ -0,0 +1,143 @@ +""" +Django settings for split_auth project. + +Generated by 'django-admin startproject' using Django 1.11.5. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os +import datetime + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '@x2k&))yy%^4*h5#c0yxr%itb42wum$89gtelwdjxy)+df22cx' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'jwt_auth', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'split_auth.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'split_auth.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' + +# #只要一个月内使用一次app,就不用重新登录,使用auth刷新最长可以一年不重新登录 +JWT_AUTH = { + 'JWT_RESPONSE_PAYLOAD_HANDLER': 'jwt_auth.serializers.jwt_response_payload_handler', + 'JWT_ALLOW_REFRESH': True, + 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30), + 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=365), +} + +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + 'jwt_auth.backends.EmailOrMobileAuthBackend', + 'jwt_auth.backends.JWTAuthentication', + ) +} + + diff --git a/split_auth/urls.py b/split_auth/urls.py new file mode 100644 index 0000000..dec3552 --- /dev/null +++ b/split_auth/urls.py @@ -0,0 +1,22 @@ +"""split_auth URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url, include +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^auth/', include("jwt_auth.urls", namespace="registration")), +] diff --git a/split_auth/wsgi.py b/split_auth/wsgi.py new file mode 100644 index 0000000..964b511 --- /dev/null +++ b/split_auth/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for split_auth project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "split_auth.settings") + +application = get_wsgi_application()