首页 > 代码库 > Django 中的用户认证
Django 中的用户认证
Django 自带一个用户认证系统,这个系统处理用户帐户、组、权限和基于 cookie 的 会话。本文说明这个系统是如何工作的。
概览
认证系统由以下部分组成:
- 用户
- 权限:控制用户进否可以执行某项任务的二进制(是/否)标志。
- 组:一种为多个用户加上标签和权限的常用方式。
- 消息:一种为指定用户生成简单消息队列的方式。
安装
认证系统打包在 Django 的 django.contrib.auth 应用中,安装它有以下步骤:
- 把 ‘django.contrib.auth‘ 和 ‘django.contrib.contenttypes‘ 放到 INSTALLED_APPS 设置中。(django.contrib.auth 中的 Permission 模型依赖于 django.contrib.contenttypes 。)
- 运行命令 manage.py syncdb 。
注意:为了方便起见, django-admin.py startproject 创建的缺省:file:settings.py 文件的 INSTALLED_APPS 中已包含 ‘django.contrib.auth‘ 和 ‘django.contrib.contenttypes‘ 。如果你的 INSTALLED_APPS 早已包含这些应用,你可以放心地再次运行 manage.py syncdb 。这个命令可以多次执行,因为每次它只会安装 需要安装的东西。
syncdb 命令创建必须的数据库表和所有已安装应用需要的权限对象。第一次 运行这个命令会提示你创建一个超级用户。
通过以上步骤,认证系统就安装完成了。
用户
class models.UserAPI 手册
字段
class models.UserUser 对象有以下字段:
username必选项。 小于等于 30 个字符。 只能是字母数字(字母、数字和下划线)。
可选项。 小于等于 30 个字符。
last_name可选项。 小于等于 30 个字符。
email可选项。电子邮件地址。
password必选项。密码(哈希值,元数据)。 Django 不储存原始密码。原始密码可以是 任意长度的,包含任何字符。参见下面“密码”一节。
is_staff布尔值。指明这个用户是否可以进入管理站点。
is_active布尔值。指明这个用户帐户是否是活动的。我们建议把这个标记设置为 False 来代替删除用户帐户,这样就不会影响指向用户的外键。
这个属性不控制用户是否可以登录。登录验证时不会核查 is_active 标志。 因此,如果在登录时需要检查is_active 标志,需要你在自己的登录视图中 实现。但是用于 login() 视图的 AuthenticationForm 会 执行这个 检查,因此应当在 Django 站点中进行认证并执行 has_perm() 之类的权限检查方法。所有那些方法或函数 对于不活动的用户都会返回 False 。
is_superuser布尔值。指明用户拥有所有权限(包括显式赋予和非显式赋予的)。
last_login缺省情况下设置为用户最后一次登录的日期时间。
date_joined缺省情况下设置为用户帐户创建的日期时间。
方法
class models.UserUser 对象有两个多对多字段 fields: models.User. 组( groups ) 和 用户权限( user_permissions ) 。User 对象可以象其它 Django 模型 一样操作关联对象:
myuser.groups = [group_list] myuser.groups.add(group, group, ...) myuser.groups.remove(group, group, ...) myuser.groups.clear() myuser.user_permissions = [permission_list] myuser.user_permissions.add(permission, permission, ...) myuser.user_permissions.remove(permission, permission, ...) myuser.user_permissions.clear()
除了那些自动 API 方法之外, User 对象 还有以下自己的方法:
is_anonymous()总是返回 False 。这是一个区别 User 和 AnonymousUser 对象的方法。 通常,你会更喜欢用is_authenticated() 来代替这个 方法。
is_authenticated()总是返回 True 。这是一个测试用户是否经过验证的方法。这并不表示任何 权限,也不测试用户是否是活动的。这只是验证用户是否合法,密码是否正确。
get_full_name()返回 first_name 加上 last_name ,中间加上一个空格。
set_password(raw_password)根据原始字符串设置用户密码,要注意密码的哈希算法。不保存 User 对象。
check_password(raw_password)如果密码正确则返回 True 。(在比较密码时要注意哈希算法。)
set_unusable_password()标记用户可以不设置密码,这与用户使用空字符串作为密码是不同的。对这种 用户, check_password() 肯定不会返回 True 。不保存 User 对象。
如果你的应用存在于如 LDAP 目录之类的外部来源时,那么可能需要这个方法。
has_usable_password()如果对于这个用户调用 set_unusable_password() 则返回 False 。
get_group_permissions(obj=None)通过用户的组返回用户的一套权限字符串。
如果有 obj 参数,则只返回这个特定对象的组权限。
get_all_permissions(obj=None)通过用户的组和用户权限返回用户的一套权限字符串。
如果有 obj 参数,则只返回这个特定对象的组权限。
has_perm(perm, obj=None)如果用户有特定的权限则返回 True ,这里的 perm 的格式为 " label>. codename>" 。(参见下面 权限 一节。) 如果用户是不活动的,这个方法总是返回 False 。
如果有 obj 参数,这个方法不会检查模型的权限,只会检查这个特定对象的 权限。
has_perms(perm_list, obj=None)如果用户有列表中每个特定的权限则返回 True ,这里的 perm 的格式为" label>. codename>" 。如果用户是不活动的,这个方法 总是返回 False 。
如果有 obj 参数,这个方法不会检查模型的权限,只会检查这个特定对象的 权限。
has_module_perms(package_name)如果用户在给定的包( Django 应用标签)中有任何一个权限则返回 True 。如果用户是不活动的,这个方法总是返回 False 。
get_and_delete_messages()在用户的列表中获得 Message 对象 列表,并从队列中删除。
email_user(subject, message, from_email=None)发送一个电子邮件给用户。如果 from_email 为 None ,则 使用 DEFAULT_FROM_EMAIL 。
get_profile()返回用户的特定站点的描述。如果当前站点不提供描述则引发django.contrib.auth.models.SiteProfileNotAvailable 。关于定义 特定站点用户描述,参见下文 储存用户的额外信息 一节。
管理器函数
class models.UserManagerUser 模型有一个包含以下实用函数的 自定义管理器:
create_user(username, email, password=None)创建、保存并返回一个 User 。
username 属性和 password 属性根据给出的参数 设置。 email 属性自动转换为 小写字母,且返回的 User 对象会有 一个设置为 True 的 is_active 属性。
如果没有提供密码,就会调用 set_unusable_password() 。
使用举例参见 创建用户 。
make_random_password(length=10,allowed_chars=‘abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789‘)根据给定的长度和给定的可用字符串返回一个随机密码。注意缺省的可用字符串 不包括以下容易混淆的字符:
- i 、 l 、 I 和 1 (小写字母 i 、小写字母 L 、大写 字母 i 和数字一)
- o 、 O 和 0 (大写字母 o 、小写字母 o 和零) and zero)
基本用法
创建用户
最基本的创建用户的方法是使用 Django 自带的 create_user() 助理函数:
>>> from django.contrib.auth.models import User >>> user = User.objects.create_user(‘john‘, ‘lennon@thebeatles.com‘, ‘johnpassword‘) # 到这里, user 是一个已存入数据库的 User 对象。如果要改变其它字段可以继续 # 改变这个对象的属性。 >>> user.is_staff = True >>> user.save()
你也可以用 Django 的管理站点来创建用户。假设你已经启用了管理站点并把站点挂接到 /admin/ ,/admin/auth/user/add/ 页面会有一个“增加用户( Add user )” 按钮。在管理站点的主页面中的“认证” 小节中还可以看见一个“用户( Users )” 链接。“增加用户”页面与标准的管理站点页面不同之处在于在允许编辑其它用户字段之前 必须确定用户名和密码。
同时要注意:在管理站点中,如果要你的帐户可以创建用户,那么必须有添加 和 变更 用户的权限。如果只有添加用户权限而没变更权限,那么是不能创建用户的。因为如果有 添加用户权限,那么就可以添加超级用户,然后就可以变更其他用户。所以 Django 需要 同时具备添加 和 变更权限。这是出于安全性考虑。
变更密码
manage.py changepassword *username* 提供了一个用 命令行方式变更用户密码的途径。如果给定了用户名,这个命令会提示你输入两次密码。 当两次输入的密码相同时,该用户的新密码会立即生效。如果没有给定用户,这个命令会 尝试改变与当前用户名匹配的用户的密码。
你也可以使用 set_password() 来变更 密码:
>>> from django.contrib.auth.models import User >>> u = User.objects.get(username__exact=‘john‘) >>> u.set_password(‘new password‘) >>> u.save()
不要直接设置 password 属性,除非必须。 下一节会说明原因。
密码
User 对象的 password 属性是一个如下格式的字符串:
hashtype$salt$hash
由哈希类型、盐值和哈希值组成,用美元符合分隔。
哈希类型可以是 sha1 (缺省值)、 md5 或 crypt ,表明使用哪种单向 哈希算法来处理密码。盐值是一个随机字符串,用于在哈希过程中加盐。注意 crypt 方法只适用于装有标准 Python crypt 模块的平台下。
密码例子:
sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
set_password() 方法和 check_password() 函数用于设置和检查 密码。
以前版本的 Django ,如 0.90 版,只是简单使用 MD5 哈希,并且不使用盐值。为了向后 兼容,这些密码还是被支持的,指定用户的密码在第一次正常使用 check_password() 后,会自动转换为新的 形式。
匿名用户
class models.AnonymousUserdjango.contrib.auth.models.AnonymousUser 是一个 django.contrib.auth.models.User 接口实现的类,区别如下:
- id 总是 None 。
- is_staff 和 is_superuser 都总是 False 。
- is_active 总是 False 。
- groups 和 user_permissions 都总是空的。
- is_anonymous() 返回 True , 而不是 False 。
- is_authenticated() 返回 False ,而不是 True 。
- set_password() 、 check_password() 、 save() 、 delete() 、 set_groups() 和set_permissions() 引发 NotImplementedError 。
在实践中,你一般不会自己使用 AnonymousUser 对象,但是 Web 请求会使用, 在下节会详述。
创建超级用户
在 INSTALLED_APPS 中添加 ‘django.contrib.auth‘ 后第一次运行 manage.py syncdb 会提示你创建一个超级用户。如何在以后单独 创建可以使用以下命令行工具:
manage.py createsuperuser --username=joe --email=joe@example.com
运行后会提供输入密码。输入密码后,用户会被立即创建。如果没有写明 --username 或 --email ,会提示你输入这两个值。
如果你使用的是老版本的 Django ,则可以使用如下命令:
python /path/to/django/contrib/auth/create_superuser.py
...这里的 /path/to 是你系统中 Django 代码所在路径。推荐使用 manage.py 命令,因为它为你提供了恰当的路径和环境。
为用户储存额外的信息
如果要储存用户相关的额外信息, Django 提供了一个方法来定义一个站点相关模型,称 之为“用户概况( user profile )”。
具体实现的方式是定义一个用于储存额外信息或附加方法的模型,同时在模型中添加一个 OneToOneField 关联到 User模型。这样就可以保证每个 User 只能创建唯一的模型实例。
为了使定义的模型成为站点的用户概况模型,必须有 AUTH_PROFILE_MODULE 设置中定义一个包含以下条目的字符串,条目间以点号分隔:
- 使用用户概况模型的应用名称(大小写敏感)。亦即用来创建应用而传递给 manage.py startapp 的名称。
- 模型的名称(大小写不敏感)。
例如,如果用户概况模型名称为 UserProfile 且定义在一个名称为 accounts 的 应用中,那么正确的设置是:
AUTH_PROFILE_MODULE = ‘accounts.UserProfile‘
当一个用户概况模型定义好并按上述方法指定之后,每个 User 对象就会有一个 get_profile() 方法,这个方法可以返回 与 User 相关联的用户概况模型。
如果用户概况不存在,则 get_profile() 方法不会创建用户概况。你必需在用户模型中为django.db.models.signals.post_save 信号注册一个句柄,并且在句柄中,如果 created=True ,则创建相关的用户概况。
更多信息,参见 Django 之书第十二章.
Web 请求中的认证
到这里本文谈得是手动处理认证相关对象的低级 API ,在更高的级别 Django 可以把这个 认证框架挂接到request objects 系统中。
首先,通过 MIDDLEWARE_CLASSES 设置安装 SessionMiddleware 和 AuthenticationMiddleware 中间件。详见session documentation 。
安装完中间件后就可以在视图中操作 request.user 了。 request.user 会给你一个 User 对象来表现当前已登录的用户。如果用户 还没有登录,那么 request.user 会被设置为 一个 AnonymousUser 的实例(参见前一节)。 你可以用于 is_authenticated() 来区别 对待,如下例:
if request.user.is_authenticated(): # 做一些已登录用户的事。 else: # 做一些匿名用户的事。
如何登录一个用户
Django 在 django.contrib.auth 模型中提供两个函数: authenticate() 和 login() .
authenticate()authenticate() 用于验证指定用户的用户名和 密码。这个函数有两个关键字参数, username 和 password ,如果密码与 用户匹配则返回一个 User 对象。否则返回 None 。例如:
from django.contrib.auth import authenticate user = authenticate(username=‘john‘, password=‘secret‘) if user is not None: if user.is_active: print "你提供了一个正确的用户名和密码!" else: print "帐户已禁用!" else: print "用户名或密码不正确。"
login() 用于在视图中登录用户。它带有一个 HttpRequest 对象和一个 User 对象。 login() 使用 Django 的会话框架在会话中保存用户 的 ID 。因此如前文所述,会话中间件必须已经安装。
本例展示如何同时使用 authenticate() 和 login():
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) # 重定向到一个登录成功页面。 else: # 返回一个“帐户已禁用”错误信息。 else: # 返回一个“非法用户名或密码”错误信息。
首先调用 authenticate()
当你手动登录一个用户时, 必须 在调用 login() 之前调用 authenticate() 。在成功验证用户后,authenticate() 会在 User 上设置一个空属性(详见 其他认证资源 ),这个信息在以后登录过程中要用到。
手动检查一个用户的密码
check_password()如果要通过比较明文密码和数据库中的哈希密码来手动认证用户,可以使用便捷的django.contrib.auth.models.check_password() 函数。它带有两个参数:要 比较的明文密码和被比较的数据库中用户 密码 字段的完整值。如果匹配则返回 True ,否则返回 False 。
如何登出一个用户
logout()在视图中可以使用 django.contrib.auth.logout() 来登出通过 django.contrib.auth.login() 登录的用户。它带有一个 HttpRequest 对象,没有返回值。 例如:
from django.contrib.auth import logout def logout_view(request): logout(request) # 重定向到一个登出成功页面。
注意当用户没有登录时,调用 logout() 函数不会 引发任何错误。
当调用 logout() 时,当前请求的会话数据会清空。 这是为了防止另一个用户使用同一个浏览器登录时会使用到前一个用户的会话数据。 如果要在用户登出后有会话中储存一些数据,那么得在 django.contrib.auth.logout() 之后 储存。
登录和登出信号
当一个用户登录或登出时认证框架使用两个用于通知的 信号 。
django.contrib.auth.signals.user_logged_in成功登录时所发的信号。
这个信号的参数:
sender 同上,登录成功的用户类。 request 当前 HttpRequest 实例。 user 登录成功的用户实例。 django.contrib.auth.signals.user_logged_out调用登出方法时发的信号。
sender 同上:登出的用户类或者 None (用户验证失败时)。 request 当前 HttpRequest 实例。 user 登出成功的用户实例或者 None (用户验证失败时)。控制非登录用户的页面权限
原始方式
页面限制的简单原始方式是检查 request.user.is_authenticated() 并且重定向到一个登录 页面:
from django.http import HttpResponseRedirect def my_view(request): if not request.user.is_authenticated(): return HttpResponseRedirect(‘/login/?next=%s‘ % request.path) # ...
...或显示一个出错页面:
def my_view(request): if not request.user.is_authenticated(): return render_to_response(‘myapp/login_error.html‘) # ...
登录要求饰件
decorators.login_required([redirect_field_name=REDIRECT_FIELD_NAME, login_url=None])做为一个捷径,可以使用方便的 login_required() 饰件:
from django.contrib.auth.decorators import login_required @login_required def my_view(request): ...
login_required() 做以下事情:
- 如果用户没有登录,那么就重定向到 settings.LOGIN_URL ,并在查询字符串中传递当前绝对 路径。例如:/accounts/login/?next=/polls/3/ 。
- 如果用户已登录,则正常的执行视图。视图代码认为用户已登录。
缺省情况下,成功登录后重定向的路径是保存在 next 参数中的。如果你想用 另一个名称,那么 login_required()可以使用一个可选的 redirect_field_name 参数:
from django.contrib.auth.decorators import login_required @login_required(redirect_field_name=‘my_redirect_field‘) def my_view(request): ...
注意,如果你提供了一个值给 redirect_field_name ,那么你最好也同样自定义 你的登录模板。因为保存重定向路径的模板环境变量会使用 redirect_field_name 作为关键值,而不使用缺省的 "next" 。
login_required() 还有一个可选的 login_url 参数。例如:
from django.contrib.auth.decorators import login_required @login_required(login_url=‘/accounts/login/‘) def my_view(request): ...
注意,如果你没有指定 login_url 参数,那么你需要为 settings.LOGIN_URL 映射适当的视图。例如,在缺省情况 下,在你的 URLconf 中加入以下一行:
(r‘^accounts/login/$‘, ‘django.contrib.auth.views.login‘),
以下是 django.contrib.auth.views.login 做了什么:
- 如果是通过 GET 调用的,则显示一个 POST 到相同 URL 的登录表单。 稍后会以更多的说明。
- 如果是通过 POST 调用的,则会尝试登录用户。如果登录成功,则会重 定向到 next 指定的 URL 。如果 next 没有提供,则重定向到 settings.LOGIN_REDIRECT_URL (缺省 的是 /accounts/profile/ )。如果登录不成功,则重新显示登录表单。
缺省情况下,在名为 registration/login.html 的模板中提供一个登录表单是 你的责任。这个模板要传递四个环境变量:
- form: 一个表现登录表单的 Form 对象。关于 Form 对象的更多内容 表单文档 。
- next: 登录成功后要重定向的 URL 。也可以包含一个查询字符串。
- site: 当前的 Site ,依赖于 SITE_ID 设置。如果没有安装站点框架,则会被设置为一个 RequestSite 实例,这个实例会使用 从当前 HttpRequest 中派生出的站点名称和域名。
- site_name: site.name 的一个别名。如果没有安装站点框架,则会被 设置为request.META[‘SERVER_NAME‘] 的 值。更多关于站点的内容参见 The "sites" framework 。
如果你不想调用模板文件 registration/login.html ,那么可以在 URLconf 通过额外参数把 template_name 传递给视图。例如,以下的 URLconf 设置会 使用 myapp/login.html 来代替缺省的模板:
(r‘^accounts/login/$‘, ‘django.contrib.auth.views.login‘, {‘template_name‘: ‘myapp/login.html‘}),
你还可以通过传递 redirect_field_name 给视图来指定包含登录后重定向 URL 的 GET 字段的名称。这个字段缺省值为 next 。
这里有一个例子模板文件 registration/login.html ,你可以作为一个 基础。它假设你有一个 base.html 模板,模板中定义了一个 content 块:
{% extends "base.html" %} {% load url from future %} {% block content %} {% if form.errors %}
Your username and password didn‘t match. Please try again.
{% endif %} method="post" action="{% url ‘django.contrib.auth.views.login‘ %}"> {% csrf_token %}
{{ form.username.label_tag }} | {{ form.username }} |
{{ form.password.label_tag }} | {{ form.password }} |
type="submit" value=http://www.mamicode.com/"login" /> type="hidden" name="next" value=http://www.mamicode.com/"{{ next }}" /> {% endblock %}
如果你使用是其它认证方式(参见 其他认证资源 ),那么可以 通过 authentication_form 参数把自定义的认证表单传递给登录视图。这个表单 必须在它的 __init__ 方法接受一个 request 关键字参数,并且提供一个 用于返回认证用户对象的 get_user 方法(这个方法只会在表单验证成功后 调用)。
其他内建视图
除了 login() 视图外,认证系统还有其他一些有用的内建视图。这些 视图都在 django.contrib.auth.views 中:
logout(request[, next_page, template_name, redirect_field_name])登出用户。
可选参数:
- next_page: 登出后转向的 URL 。
- template_name: 登出后要显示的模板的全名。如果没有提供这个参数, 缺省为registration/logged_out.html 。
- redirect_field_name: 包含登出后转向的 URL 的 GET 字段的名称。 如果给出则覆盖 next_page 。
模板语境:
- title: 本地化的 "Logged out" 字符串。
登出用户,并重定向到登录页面。
可选参数:
- login_url: 要重定向到的登录页面的 URL 。如果没有给出这个参数,则 缺省为 settings.LOGIN_URL 。
允许一个用户更改自己的密码。
可选参数:
-
template_name: 显示用户更改页面的模板全名。如果没有给出,则缺省为registration/password_change_form.html 。
-
post_change_redirect: 成功更改密码后要转向到的 URL 。
-
New in Django 1.2: Please, see the release notes
password_change_form: 一个自定义的“更改密码”的表单,必须接受一个 user 关键字参数。这个表单负责改变用户密码。
模板语境:
- form: 密码变更表单。
用户变更密码后要显示的页面。
可选参数:
- template_name: 模板的全名。缺省为 registration/password_change_done.html 。
生成一个用于用户重置密码的一次性链接,并通过电子邮件发送这个链接。
可选参数:
- template_name: 用于显示密码重置页面的模板全名。如果没有给出,则 缺省为registration/password_reset_form.html 。
- email_template_name: 用于生成包含新密码的电子邮件的页面的模板 全名。如果没有给出,则缺省为registration/password_reset_email.html 。
- password_reset_form: 用于设置密码的表单。缺省为 PasswordResetForm 。
- token_generator: 检查密码的类的实例。缺省为 default_token_generator ,它是一个django.contrib.auth.tokens.PasswordResetTokenGenerator 的实例。
- post_reset_redirect: 成功变更密码后要转向的 URL 。
- from_email: 一个可用的电子邮件地址。缺省情况下 Django 使用 DEFAULT_FROM_EMAIL 。
模板语境:
- form: 用于重置密码的表单。
重置密码显示的页面。
可选参数:
- template_name: 模板的全名。缺省为 registration/password_reset_done.html 。
重定向到登录页面,成功登录后回到另一个 URL 。
必选参数:
- next: 成功登录后到转向的 URL 。
可选参数:
- login_url: 登录页面的 URL 。缺省为 settings.LOGIN_URL 。
- redirect_field_name: 包含登录后要转向的 URL 的 GET 字段的 名称。如果给出则覆盖 next 。
给出一个输入新密码的表单。
可选参数:
- uidb36: 用户的 id 以 base 36 方式编码。缺省为 None 。
- token: 检查密码是否有效的标志。缺省为 None.
- template_name: 显示确认密码视图的模板的全名。缺省为registration/password_reset_confirm.html 。
- token_generator: 检查密码的类的实例。缺省为 default_token_generator ,它是一个django.contrib.auth.tokens.PasswordResetTokenGenerator 的实例。
- set_password_form: 用于设置密码的表单。缺省为 SetPasswordForm 。
- post_reset_redirect: 重置密码的要转向的 URL 。缺省为 None 。
展现一个通知用户密码已成功变更的视图。
可选参数:
- template_name: 显示视图的模板的全名。缺省为 registration/password_reset_complete.html 。
内建表单
如果你不想使用内建的视图,但是想自己写表单,那么认证系统在 django.contrib.auth.forms 中提供了一些内建的表单:
class AdminPasswordChangeForm在管理接口中改变用户密码的表单。
class AuthenticationForm登录表单。
class PasswordChangeForm变更密码表单。
class PasswordResetForm生成重置密码链接并通过电子邮件发送链接的表单。
class SetPasswordForm不需要输入旧密码就可以变更用户密码的表单。
class UserChangeForm在管理接口中变更用户信息和权限的表单。
class UserCreationForm创建新用户的表单。
通过测试来限制页面操作
要基于一定的权限或测试来限制页面的操作,其本质上和上一节的内容相同。
最简单的方式是在社图中直接在 request.user 上直接运行你的测试。例如,这个视图调试用户是否 已登录并拥有polls.can_vote 权限:
def my_view(request): if not request.user.has_perm(‘polls.can_vote‘): return HttpResponse("在这个选举中你不能投票。") # ...
作为一个捷径,你可使用方便的 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() 有一个必选参数: 带有一个 User 对象的可调用的函数,并且 如果用户被允许查看页面时返回 True 。注意 user_passes_test() 不自动检查 User 是否是匿名用户。
user_passes_test() 有一个可选的 login_url 参数,这个参数让你指定登录页面的 URL (缺省为settings.LOGIN_URL )。
例如:
from django.contrib.auth.decorators import user_passes_test @user_passes_test(lambda u: u.has_perm(‘polls.can_vote‘), login_url=‘/login/‘) def my_view(request): ...
权限需求装饰件
permission_required()检查用户是否有特定的权限是一个比较常见的任务。因此, Django 为此提供了一个 捷径:permission_required() 装饰件。 使用这个装饰件,先前的例子可以写成:
from django.contrib.auth.decorators import permission_required def my_view(request): ... my_view = permission_required(‘polls.can_vote‘)(my_view)
至于 User.has_perm() 方法,权限名称以 " label>. codename>" 形式组成(例如polls.can_vote 就是 poll 应用中的模型中的一个权限)。
注意 permission_required() 也有一个可选的 login_url 参数。例如:
from django.contrib.auth.decorators import permission_required def my_view(request): ... my_view = permission_required(‘polls.can_vote‘, login_url=‘/loginpage/‘)(my_view)
在 login_required() 装饰件中 login_url 缺省为 settings.LOGIN_URL 。
限制通用视图的操作
要限制一个 通用视图 操作,可以写一个视图的薄包装, 然后把你的 URLconf 指向薄包装而不是通用视图本身。例如:
from django.views.generic.date_based import object_detail @login_required def limited_object_detail(*args, **kwargs): return object_detail(*args, **kwargs)
权限
Django 自带了一个简单的权限系统,它可以管理特定的用户和用户组的权限。
这个系统是用于 Django 管理站点的,但是欢迎在你自己的代码中使用它。
Django 管理站点使用如下权限:
- 查看“增加”表单和增加对象需要有该种对象的“增加”权限。
- 查看变更列表、“变更”表单和变更对象需要有该种对象的“变更”权限。
- 删除对象需要有该种对象的“删除”权限。
权限是针对某种对象的全局性的设置,而不是针对某种对象的某个特定实例的。例如, 我们可以说“玛丽可以改写故事”,但是不能说“玛丽可以改写故事,但只限于她自己的 故事”,也不能说“玛丽只能改写符合特定条件(如出版日期、编号)的故事”。后面的 两种情况是 Django 的开发者正在讨论的东西,现在还不支持。
缺省权限
当 django.contrib.auth 存在于你的 INSTALLED_APPS 设置中时,会 确保你安装的应用中每个 Django 模型在三个缺省的权限——增加、变更和删除。
当你运行 manage.py syncdb 时这些权限会被创建。在向 INSTALLED_APPS 增加 django.contrib.auth 后第一次运行 syncdb 时,会为以前已安装的模型和正在安装的模型创建缺省权限。以后每次运行 manage.py syncdb 都会为所有模型创建缺省权限。
假设你的应用有一个名为 foo 的 app_label 和一个名为 Bar 的模型,要测试基本的权限可以这样:
* add: ``user.has_perm(‘foo.add_bar‘)`` * change: ``user.has_perm(‘foo.change_bar‘)`` * delete: ``user.has_perm(‘foo.delete_bar‘)``
自定义权限
使用 权限 模型元属性 可以创建自定义权限。
下例中的 Task 模型创建了三个自定义权限:
class Task(models.Model): ... class Meta: permissions = ( ("can_view", "Can see available tasks"), ("can_change_status", "Can change the status of tasks"), ("can_close", "Can remove a task by setting its status as closed"), )
当运行 manage.py syncdb 时以上代码只做了一件事:创建了三个 额外的权限。当用户尝试操作应用提供的功能(查看任务、改变任务状态和关闭任务) 时,你的代码必须负责检查权限的值。
API 手册
class models.Permission和用户一样,权限也在 django/contrib/auth/models.py Django 模型中实现。
字段
Permission 对象有以下字段:
Permission.name必需的。 50 个字符以下。例如: ‘Can vote‘ 。
Permission.content_type属于的。一个 django_content_type 数据库表的引用,包含每一个已安装的 Django 模型。
Permission.codename必需的。 100 个字符以下。例如: ‘Can_vote‘ 。
方法
Permission 对象和其它 Django 模型 一样有标准数据操作方法。
模板中的认证数据
当你使用 RequestContext 时,当前已登录的用户 及其权限在 模板环境 中提供。
技术细节
在技术细节上,这些变量只有当你使用 RequestContext , 并且 你的TEMPLATE_CONTEXT_PROCESSORS 设置中包含"django.contrib.auth.context_processors.auth" 时才在模板环境中可用,这是 缺省 情况。更多见容,详见 请求环境文档 。
用户
当渲染一个模板 RequestContext 时,当前已登录 的用户(要么是一个 User 实例,要么是一个 AnonymousUser 实例)被储存在模板变量 {{ user }} 中:
{% if user.is_authenticated %}
欢迎, {{ user.username }} 。谢谢登录。
{% else %}
欢迎,新用户。请登录。
{% endif %}
如果一个 RequestContext 未使用则这个模板环境变量不可用。
权限
当前已登录用户的权限储存在模板变量 {{ perms }} 中。这是一个django.contrib.auth.context_processors.PermWrapper 的实例。它是一个 模板友好的权限的代理。
在 {{ perms }} 对象中,单个属性的查找是 User.has_module_perms 方法的一个代理。
如果已登录用户在 foo 应用中有权限,那么以下例子会显示 True
{{ perms.foo }}
二维属性查找是 User.has_perm 方法的一个代理。如果已登录用户有 foo.can_vote 权限,那么以下例子会显示 True
{{ perms.foo.can_vote }}
然而,你可以在模板 {% if %} 语句中检查权限:
{% if perms.foo %}
你在 foo 应用中有权限。
{% if perms.foo.can_vote %}
你可以投票!
{% endif %} {% if perms.foo.can_drive %}
你可以驾驶!
{% endif %} {% else %}
你在 foo 应用中没有权限。
{% endif %}
组
组是一种把用户分类以便于分配权限或打上标签的常用方式。一个用户可以属于任意多个 组。
组中的用户自动被赋予这个组所拥有的权限。例如,如果 网站编辑 组拥有 can_edit_home_page 权限,那么这个组中的用户都有这个权限。
除了权限外,组还是一个把用户分类以便于打上标签或给予特定功能的便捷方式。例如, 你可创建一个 特别用户 组,然后就可以为这个编写特定的程序,给予站点特定的 部分的全员专有访问权限或发送会员专有电子邮件等等。
消息
消息系统是一个指定用户的消息队列的轻度实现。
每个消息与一个 User 相关联。消息没有期限或 时间戳。
消息用于 Django 管理站点提示某个动作已成功完成。例如 "投票已成功创建" 就是 一个消息。
API 很简单:
models.User.message_set.create(message)使用 user_obj.message_set.create(message=‘message_text‘) 可以创建一个新 消息。
取得或删除消息使用user_obj.get_and_delete_messages() `。 这个方法从用户的消息队列中取回(如果有消息的话)一个 ``消息`()对象的列表并 在队列中删除这些消息。
在以下的例子视图中,系统为用户保存了创建节目单后产生的一个消息:
def create_playlist(request, songs): # Create the playlist with the given songs. # ... request.user.message_set.create(message="你的节目单已成功添加。") return render_to_response("playlists/create.html", context_instance=RequestContext(request))
当你使用 RequestContext 时,当前的已登录用户 及其消息可由 模板环境 中模板变量 {{ messages }} 来提供。下面就是显示消息的模板例子:
{% if messages %}
{% for message in messages %}
- {{ message }}
{% endif %}
最后,注意消息框架只能用于用户数据库中已存在的用户。如果要向匿名用户发送消息, 请使用 消息框架 。
其他认证资源
Django 自带的认证系统对于一般情况已经够用了,但是你可能需要挂接其他的认证资源, 即另一个用户名和密码资源或认证方法。
例如,你的公司可能已有一个 LDAP ,其中已储存了每个员工的用户名和密码。如果在 LDAP 和 基于 Django 的应用中使用两套帐户,那么对于管理员和用户来说都是不方便 的。
因此,为了应对这种情况, Django 的认证系统可以让你挂接其他认证资源。你可以重载 Django 的缺省数据库计划或使缺省系统与其他系统协作。
关于 Django 认证后台处理的内容参见 认证后台参考 。
指定认证后端
在后台, Django 维护着一个用于认证的“认证后端”列表。当象前文 如何登录一个用户 一节中提到的,使用django.contrib.auth.authenticate() 时, Django 会尝试列表中的所有 后端。如果第一个不行,则尝试第二个,以此类推,直到最后一个。
认证后端的列表中 AUTHENTICATION_BACKENDS 设置中定义。设置内容应当是 一个指向可以用于认证的 Python 类的路径名的元组。这些类可以放在 Python 路径下的 任意位置。
缺省情况下, AUTHENTICATION_BACKENDS 被设置为:
(‘django.contrib.auth.backends.ModelBackend‘,)
这是检查 Django 用户数据库的基本认证方案。
AUTHENTICATION_BACKENDS 的顺序是重要的,所以如果相同的用户名和密码在 多个后端中同时存在, Django 会在第一个匹配的地方停止。
Note
一旦一个用户通过认证, Django 会在会话中储存该用户所使用的后端,并且在随后 的认证中使用已储存的后端。即认证资源会被缓存,如果你改变 AUTHENTICATION_BACKENDS ,并且要强迫用户使用不同的后端重新认证, 那么需要清除会话数据。一个简单的方法是执行 Session.objects.all().delete()。
编写一个认证后端
一个认证后端是一个执行两个方法的类: get_user(user_id) 和 authenticate(**credentials) 。
get_user 方法需要一个 user_id (可以是一个用户名、数据库 ID 或其他 东东),并且返回一个 用户 对象。
authenticate 方法有一个证书关键字参数。多数情况下看上去如下:
class MyBackend: def authenticate(self, username=None, password=None): # 检查用户名和密码并返回一个用户。
但是也可以认证一个标志,象下面这样:
class MyBackend: def authenticate(self, token=None): # 检查标志并返回一个用户。
不管怎样, authenticate 应当检查得到的材料。当材料有效时,返回一个符合条件 的 用户 对象,否则返回 None 。
Django 管理系统与本文开头所谈的 Django 用户 对象是紧密关联的。目前,最好的 办法是为你的认证后端中存在的每个用户创建一个 Django 用户 对象(例如,在你的 LDAP 目录中,你的 SQL 数据库中等等)。你要么写一个脚本,要么你的 认证 方法 在第一次用户登录时来创建 用户 对象。
以下是一个后端举例,它检查 settings.py 文件中定义的用户名和密码,并且在用户 第一次登录时创建一个 用户 对象:
from django.conf import settings from django.contrib.auth.models import User, check_password class SettingsBackend: """ 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_object_permissions = False supports_anonymous_user = False 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
在自定义后端中处理认证
自定义后端可以提供自己的权限。
用户模型会把权限查找函数 (:meth:~django.contrib.auth.models.User.get_group_permissions() 、get_all_permissions() 、 has_perm() 和 has_module_perms() )委托给执行这些 函数的任何一个后端。
提供给用户的权限将会是所有后端返回的权限的超集。即 Django 会把任一个后端的权限 都提供给用户。
上面的简单的后端可以相当简单的执行神奇的权限管理:
class SettingsBackend: # ... def has_perm(self, user_obj, perm): if user_obj.username == settings.ADMIN_LOGIN: return True else: return False
上例中通过验证的用户得到了所有权限。注意,后端认证函数都把用户对象作为一个参数 并且接受与django.contrib.auth.models.User 同样的参数。
一个完整的认证实现参见 django/contrib/auth/backends.py 。通常情况下这是缺省 后端,查询的是 auth_permission表。
匿名用户认证
匿名用户是指没有经过认证的用户,如没有提供有效认证信息的用户。但是不等于说匿名 用户就什么事也不能做。多数网站可以允许匿名用户浏览大多数网页,而且很多网站还允许 匿名用户发表评论等。
Django 认证框架中没有地方储存匿名用户的权限,但是它允许自定义认证后端来处理匿名 用户的认证。这个能力在重用应用时尤其有用,这样不用设置就可以解决认证后端的所有 问题。
在你的认证后端中要启用匿名用户认证,必须把类属性 supports_anonymous_user 设置为 True 。(这样是为了认证后端的兼容性,确保 所有用户对象都是 django.contrib.auth.models.User 类的真正实例。)这样,django.contrib.auth.models.AnonymousUser 就会把相应的权限方法委派给认证 后端。
在 Django 1.2 中如果不存在 supports_anonymous_user 属性会引发一个隐藏的 PendingDeprecationWarning 警告。在 Django 1.3 中,这个警告会升级为一个显眼 的 DeprecationWarning 警告。另外,supports_anonymous_user 会被设置 为 False 。 Django 1.4 会假设每一个后端都支持把匿名用户传递给认证方法。
非活动用户的认证
一个非活动用户是指已经被认证过但是 is_active 属性为 False 的用户。但是 非活动用户是经过认证的,还是有一定权限的,比如他们被允许激活他们的帐户。
在权限系统中因为有对匿名用户的支持,所以匿名用户有一定的权限而不活动用户则没有。
要使非活动用户有一定权限,必须把类属性 supports_inactive_user 设置为 True 。
在 Django 1.3 中如果不存在 supports_inactive_user 属性会引发一个 PendingDeprecationWarning 警告。在 Django 1.4 中,这个警告会升级为一个显眼 的 DeprecationWarning 警告。另外, supports_inactive_user 会被设置为 False 。 Django 1.5 会假设每一个后端都支持把非活动用户传递给认证方法。
处理对象权限
Django 的权限框架支持对象权限。但是不是在框架的核心中实现的,这就意味着在检查 对象权限时总是返回 False 或一个空列表(取决于如何检查)。
要在你自己的 认证后端 中使用对象权限,只要允许向权限 传递一个 obj 参数并且设置supports_object_permissions 类属性为 True 。
在 Django 1.2 中一个不存在的 supports_object_permissions 属性会引发一个隐藏 的PendingDeprecationWarning 。在 Django 1.3 中,这个警告会升级为一个 DeprecationWarning ,非常显眼。附加的 supports_object_permissions 会被 设置为 False 。 Django 1.4 会假设每一个后端都支持对象权限并且不会检查 supports_object_permissions 是否存在,意即不支持 obj 作为一个参数就会 引发一个 TypeError 。
Django 中的用户认证