django 用户认证系统

django认证系统包含三个部分:用户、权限和分组

安装
    django项目默认启用了认证系统,如果不是使用django-admin.py创建项目的可以通过在settings配置文件里面的INSTALLED_APPS的列表里面添加django.contrib.auth和django.contrib.contenttypes这两项然后运行manage.py syncdb命令创建对应的数据库表即可

用户Users
    在Django-1.4.10\django\contrib\auth这个目录下有一个model文件,里面有Permission、GroupManager、Group、UserManager、User、AnonymousUser这些类的源码,建议自己都去看一下,很多注释,属性名和方法名都通俗易懂,这里只点一下关键点。

  • groups和user_permissions是是多对多的属性,分别对应到了类Group和Permission
  • is_active这个属性提醒我们不要轻易的删掉一些对象,我们可以设置一个标志位标识该对象是否可用
  • set_unusable_password,标识该用户没有密码设置,注意不等同与空密码

基本的用法
创建用户

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
>>> user.save()

修改密码,注意是使用set_password而不是password属性

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username__exact='john')
>>> u.set_password('new password')
>>> u.save()

创建超级用户

manage.py createsuperuser --username=joe --email=joe@example.com

存储用户的额外信息

这是一个比较麻烦的事情,不过django还是提供了一个定制的方法

首先你要定义一个模型,在这个模型里面你可以定制额外的属性或者方法,然后记得添加一个名为user的一对一的属性名

from django.contrib.auth.models import User

class UserProfile(models.Model):
    # 必选
    user = models.OneToOneField(User)

    # 自定义的属性或者方法
    accepted_eula = models.BooleanField()
    favorite_animal = models.CharField(max_length=20, default="Dragons.")

为了表明这个模型是对于那个给定的站点,我们还需要配置一个AUTH_PROFILE_MODULE,这是一个字符串,包含两部分信息,由点号相连
  • app名:大小写敏感,一般是你使用manage.py startapp创建时用的名称
  • 你自定义的模型名称,大小写不敏感

比如,app名为accounts,模型名为UserProfile

AUTH_PROFILE_MODULE = 'accounts.UserProfile'

一旦一个用户档案模型(额外信息模型)被定义然后用上述方法指明,每一个user对象都会有一个方法--get_profile()--去返回跟该用户相关的额外信息

然而,你必须注册一个到django.db.models.signals.post_save信号的处理程序,并且在处理程序里面,如果created为真,才创建关联的用户额外信息

# in models.py

from django.contrib.auth.models import User
from django.db.models.signals import post_save

# definition of UserProfile from above
# ...

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

post_save.connect(create_user_profile, sender=User)

把UserProfile添加到admin

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import UserProfile

# Define an inline admin descriptor for UserProfile model
# which acts a bit like a singleton
class UserProfileInline(admin.StackedInline):
    model = UserProfile
    can_delete = False
    verbose_name_plural = 'profile'

# Define a new User admin
class UserAdmin(UserAdmin):
    inlines = (UserProfileInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

匿名用户

我们来看一下匿名用户的属性,是对用户的一个补充

class AnonymousUser(object):
    id = None
    username = ''
    is_staff = False
    is_active = False
    is_superuser = False
    _groups = EmptyManager()
    _user_permissions = EmptyManager()

web请求中的认证

前面我们只是谈到了操纵认证相关对象的底层的APIs,在更高的层次,django可以把认证框架钩进请求对象request系统中

首先,安装会话中间件和认证中间件(在MIDDLEWARE_CLASSES)里面添加SessionMiddleware和AuthenticationMiddleware,安装好这两个中间件后,你可以在视图函数里面是用request.user(代表当前已经登陆的user对象,如果用户还没等,将代表一个匿名对象),可以使用is_authencated()方法来辨别是否已经登陆

如何登陆用户

django提供了两个函数django.contrib.auth:authenticate()和login()

authentecate()

用给定的用户名和密码去认证,返回一个User对象或者None

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    if user.is_active:
        print "You provided a correct username and password!"
    else:
        print "Your account has been disabled!"
else:
    print "Your username and password were incorrect."

login()

在视图函数中可以使用login()方法去登陆一个用户,这个方法需要一个HttpRequest对象和一个User对象,login()函数把用户ID存在session里面(是用django的session框架,所以请确保启用了会话中间件),如果是手工登陆用户,请先条用authenticate()方法

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        if user.is_active:
            login(request, user)
            # Redirect to a success page.
        else:
            # Return a 'disabled account' error message
    else:
        # Return an 'invalid login' error message.

如何注销用户

logout()

去注销一个使用django.contrib.auth.login()方法登陆的用户,请使用在视图函数中使用django.contrib.auth.logout()方法注销,该方法需要一个HttpRequest对象并且没有返回值

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

限制登陆用户的访问

原始方法

在视图函数中检查request.user.is_authencated()是否为真,从而决定是重定向到一个登陆页面或者是错误页面

login_required装饰器

decorators.login_required([redirect_field_name=REDIRECT_FIELD_NAMElogin_url=None])
作为一个快捷方式,可以直接使用login_required()装饰器

from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):

这个装饰器按照以下步骤:

  • 如果用户没有登陆,重定向到settings.LOGIN_URL(把在查询字符串中的当前绝对路径传参过去,例如/accounts/login/?next=/polls/3/
  • 如果用户已经登陆,正常执行视图函数

默认情况下,用户在成功认证后的重定向路径被存在查询字符串中的next参数中,如果你想修改的话,请使用redirect_field_name参数@login_required(redirect_field_name='my_redirect_field')
注意的是,如果你提供了redirect_field_name,那么你很有可能需要去自定义登陆模板,你可以是用login_url参数@login_required(login_url='/accounts/login/')

内建视图函数

除了上面提到的login和logout,还有以下内建的视图函数:
logout_then_login(request[, login_url]):注销一个用户然后重定向到一个登陆页面
password_change(request[, template_namepost_change_redirectpassword_change_form]):允许用户修改他们的密码
password_change_done(request[, template_name]):用户修改密码后的页面
password_reset(request[, is_admin_sitetemplate_nameemail_template_name,password_reset_formtoken_generatorpost_reset_redirectfrom_email]):通过生成的一个一次性的用来重置密码的发往他们注册邮箱的链接来允许用户重置他们的密码
password_reset_done(request[, template_name]):重置密码后的页面
password_reset_confirm(request[, uidb36tokentemplate_nametoken_generator,set_password_formpost_reset_redirect]):展示一个用来输入密码的表单
redirect_to_login(next[, login_urlredirect_field_name]):重定向到一个登陆页面然后在成功登陆后转向另一个url

内建表单

如果你不想使用上面的内建视图函数,但是又不想自己写那些表单,你可以是用这些内建的表单,这些内建的表单都位于django.contrib.zuth.forms里面

class AdminPasswordChangeForm
    A form used in the admin interface to change a user’s password.
class AuthenticationForm
    A form for logging a user in.
class PasswordChangeForm
    A form for allowing a user to change their password.
class PasswordResetForm
    A form for generating and emailing a one-time use link to reset a user’s password.
class SetPasswordForm
    A form that lets a user change his/her password without entering the old password.
class UserChangeForm
    A form used in the admin interface to change a user’s information and permissions.
class UserCreationForm
    A form for creating a new user.

限制通过测试登陆的用户的访问

有时候需要检查用户是否有某些权限,或者需要通过其他的测试等等才能访问,比如下面的代码:需要检测用户是否有投票的权限

def my_view(request):
    if not request.user.has_perm('polls.can_vote'):
        return HttpResponse("You can't vote in this poll.")

user_passes_test(func[, login_url=None]),你可以简单的使用user_passes_test

from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.has_perm('polls.can_vote'))
def my_view(request):

如果你仅仅是想要检测用户是否有某项权限,你可以是用更简单的permission_required装饰器,user_passes_test不会检测用户是否是匿名用户,只是检查是否能通过测试,这点是值得注意的,另外,如果用户没有通过测试,你可以定义login_url来重定向到一个登陆页面,如:

@user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')

permission_required装饰器

permission_required([login_url=Noneraise_exception=False]):检查用户是否具有特定的权限,可以自定义用户不具有要求权限是重定向到登陆页面,以及是否抛出异常等等

from django.contrib.auth.decorators import permission_required
@permission_required('polls.can_vote', login_url='/loginpage/')
def my_view(request):

权限

上面说了很多关于权限的内容,下面我们看看django的权限系统吧

django自带一个简单的权限系统 ,提供了给特定用户和组用户赋予权限的方法  ,在django的admin站点被使用,同时你也可以在自己的代码中使用      

默认权限

当django.contrib.auth被加入INSTALLED_APPS的时候,三项特别的权限--添加,修改和删除--已经为每个django模型创建好了,这三项权限是在你运行manage.py syncdb的时候创建的  

假设你有个应用的app_label是foo,一个模型名为Bar,那么你可以这样来测试这三个权限    

  • add: user.has_perm('foo.add_bar')
  • change: user.has_perm('foo.change_bar')
  • delete: user.has_perm('foo.delete_bar')

自定义权限

如果要自定义权限的话,请使用permissions这个meta属性,例如:

class Task(models.Model):
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

直接在程序中创建权限

from myapp.models import BlogPost
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(codename='can_publish',
                                       name='Can Publish Posts',
                                       content_type=content_type)

这个方法与上面的方法相比,

权限API

直接看一下permission类的源码

class Permission(models.Model):
    name = models.CharField(_('name'), max_length=50)
    content_type = models.ForeignKey(ContentType)
    codename = models.CharField(_('codename'), max_length=100)
    objects = PermissionManager()

模板中的认证数据

当你使用RequestContent的时候,当前已经登陆的用户和其权限在模板上下文中时可用的

 Users

当前已经登陆的用户(不管是否匿名),数据被存在模板变量{{user}}里面(前提是RequestContext被使用)

{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
    <p>Welcome, new user. Please log in.</p>
{% endif %}

Permissions

当前已经登陆的用户的权限被存在模板变量{{perms}}里面

{% if perms.foo %}
    <p>You have permission to do something in the foo app.</p>
    {% if perms.foo.can_vote %}
        <p>You can vote!</p>
    {% endif %}
    {% if perms.foo.can_drive %}
        <p>You can drive!</p>
    {% endif %}
{% else %}
    <p>You don't have permission to do anything in the foo app.</p>
{% endif %}

分组

看过了用户和权限,我们继续看分组吧

分组是最简单的归类的方法,分组之后你可以对组内的用户分配特定的权限或者其他label;当然,一个用户可以属于多个组;一个组里面的用户自动获取改分组拥有的权限

apis

我们直接看代码吧,哈哈,就只有组名和对应的权限两项

class Group(models.Model):
    name = models.CharField(_('name'), max_length=80, unique=True)
    permissions = models.ManyToManyField(Permission,
        verbose_name=_('permissions'), blank=True)

    objects = GroupManager()

其他的认证源

一般django自带的认证系统已经满足了大部分情况下的需求,但如果你有新的的需求的时候,你可以是用其他的认证源

具体化认证后端

django有一个检查用户名密码的“认证后端”的列表,django会从列表的第一项开始尝试,直到找到匹配的一项或者最后一项位置,你可以是用AUTHENTICATION_BACKENS设置你的认证后端列表

自己写一个认证后端

一个认证后端是指一个实现了两个必选方法和一系列可选权限相关的方法的类:get_user(user_id)和authenticate(**credentials)(get_group_permissions()get_all_permissions(),has_perm(), and has_module_perms()))

其中get_user(user_id)中的user_id可以是用户名,数据库ID或者其他 ,返回一个用户对象

authenticate把credentials的内容作为关键字参数 ,可能是这样

class MyBackend(object):
    def authenticate(self, username=None, password=None):
        # Check the username/password and return a User.

或者是这样

class MyBackend(object):
    def authenticate(self, token=None):
        # Check the token and return a User.

无论如何,authenticate都应该验证credentials中的内容,然后返回一个符合哪些验证条件的用户对象,或者None

这是一个后端的例子

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend(object):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name, and a hash of the password. For example:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """

    supports_inactive_user = False

    def authenticate(self, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. Note that we can set password
                # to anything, because it won't be checked; the password
                # from settings.py will.
                user = User(username=username, password='get from settings.py')
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None