分类: Web开发
2016-06-30 12:14:54
原文地址:Django 中的用户认证 作者:刘一痕
认证系统由以下部分组成:
认证系统打包在 Django 的 django.contrib.auth 应用中,安装它有以下步骤:
注意:为了方便起见, 创建的缺省:file:settings.py 文件的 中已包含 'django.contrib.auth' 和 'django.contrib.contenttypes' 。如果你的 早已包含这些应用,你可以放心地再次运行 。这个命令可以多次执行,因为每次它只会安装 需要安装的东西。
命令创建必须的数据库表和所有已安装应用需要的权限对象。第一次 运行这个命令会提示你创建一个超级用户。
通过以上步骤,认证系统就安装完成了。
对象有以下字段:
username必选项。 小于等于 30 个字符。 只能是字母数字(字母、数字和下划线)。
可选项。 小于等于 30 个字符。
last_name可选项。 小于等于 30 个字符。
email可选项。电子邮件地址。
password必选项。密码(哈希值,元数据)。 Django 不储存原始密码。原始密码可以是 任意长度的,包含任何字符。参见下面“密码”一节。
is_staff布尔值。指明这个用户是否可以进入管理站点。
is_active布尔值。指明这个用户帐户是否是活动的。我们建议把这个标记设置为 False 来代替删除用户帐户,这样就不会影响指向用户的外键。
这个属性不控制用户是否可以登录。登录验证时不会核查 is_active 标志。 因此,如果在登录时需要检查is_active 标志,需要你在自己的登录视图中 实现。但是用于 视图的 会 执行这个 检查,因此应当在 Django 站点中进行认证并执行 之类的权限检查方法。所有那些方法或函数 对于不活动的用户都会返回 False 。
is_superuser布尔值。指明用户拥有所有权限(包括显式赋予和非显式赋予的)。
last_login缺省情况下设置为用户最后一次登录的日期时间。
date_joined缺省情况下设置为用户帐户创建的日期时间。
对象有两个多对多字段 fields: models.User. 组( groups ) 和 用户权限( user_permissions ) 。 对象可以象其它 一样操作关联对象:
除了那些自动 API 方法之外, 对象 还有以下自己的方法:
is_anonymous()总是返回 False 。这是一个区别 和 对象的方法。 通常,你会更喜欢用 来代替这个 方法。
is_authenticated()总是返回 True 。这是一个测试用户是否经过验证的方法。这并不表示任何 权限,也不测试用户是否是活动的。这只是验证用户是否合法,密码是否正确。
get_full_name()返回 加上 ,中间加上一个空格。
set_password(raw_password)根据原始字符串设置用户密码,要注意密码的哈希算法。不保存 对象。
check_password(raw_password)如果密码正确则返回 True 。(在比较密码时要注意哈希算法。)
set_unusable_password()标记用户可以不设置密码,这与用户使用空字符串作为密码是不同的。对这种 用户, 肯定不会返回 True 。不保存 对象。
如果你的应用存在于如 LDAP 目录之类的外部来源时,那么可能需要这个方法。
has_usable_password()如果对于这个用户调用 则返回 False 。
get_group_permissions(obj=None)通过用户的组返回用户的一套权限字符串。
如果有 obj 参数,则只返回这个特定对象的组权限。
get_all_permissions(obj=None)通过用户的组和用户权限返回用户的一套权限字符串。
如果有 obj 参数,则只返回这个特定对象的组权限。
has_perm(perm, obj=None)
如果用户有特定的权限则返回 True ,这里的 perm 的格式为 "
如果有 obj 参数,这个方法不会检查模型的权限,只会检查这个特定对象的 权限。
has_perms(perm_list, obj=None)
如果用户有列表中每个特定的权限则返回 True ,这里的 perm 的格式为"
如果有 obj 参数,这个方法不会检查模型的权限,只会检查这个特定对象的 权限。
has_module_perms(package_name)如果用户在给定的包( Django 应用标签)中有任何一个权限则返回 True 。如果用户是不活动的,这个方法总是返回 False 。
get_and_delete_messages()在用户的列表中获得 Message 对象 列表,并从队列中删除。
email_user(subject, message, from_email=None)发送一个电子邮件给用户。如果 from_email 为 None ,则 使用 。
get_profile()返回用户的特定站点的描述。如果当前站点不提供描述则引发django.contrib.auth.models.SiteProfileNotAvailable 。关于定义 特定站点用户描述,参见下文 储存用户的额外信息 一节。
模型有一个包含以下实用函数的 自定义管理器:
create_user(username, email, password=None)创建、保存并返回一个 。
属性和 属性根据给出的参数 设置。 属性自动转换为 小写字母,且返回的 对象会有 一个设置为 True 的 属性。
如果没有提供密码,就会调用 。
使用举例参见 。
make_random_password(length=10,allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')根据给定的长度和给定的可用字符串返回一个随机密码。注意缺省的可用字符串 不包括以下容易混淆的字符:
最基本的创建用户的方法是使用 Django 自带的 助理函数:
你也可以用 Django 的管理站点来创建用户。假设你已经启用了管理站点并把站点挂接到 /admin/ ,/admin/auth/user/add/ 页面会有一个“增加用户( Add user )” 按钮。在管理站点的主页面中的“认证” 小节中还可以看见一个“用户( Users )” 链接。“增加用户”页面与标准的管理站点页面不同之处在于在允许编辑其它用户字段之前 必须确定用户名和密码。
同时要注意:在管理站点中,如果要你的帐户可以创建用户,那么必须有添加 和 变更 用户的权限。如果只有添加用户权限而没变更权限,那么是不能创建用户的。因为如果有 添加用户权限,那么就可以添加超级用户,然后就可以变更其他用户。所以 Django 需要 同时具备添加 和 变更权限。这是出于安全性考虑。
提供了一个用 命令行方式变更用户密码的途径。如果给定了用户名,这个命令会提示你输入两次密码。 当两次输入的密码相同时,该用户的新密码会立即生效。如果没有给定用户,这个命令会 尝试改变与当前用户名匹配的用户的密码。
你也可以使用 来变更 密码:
不要直接设置 属性,除非必须。 下一节会说明原因。
对象的 属性是一个如下格式的字符串:
由哈希类型、盐值和哈希值组成,用美元符合分隔。
哈希类型可以是 sha1 (缺省值)、 md5 或 crypt ,表明使用哪种单向 哈希算法来处理密码。盐值是一个随机字符串,用于在哈希过程中加盐。注意 crypt 方法只适用于装有标准 Python crypt 模块的平台下。
密码例子:
方法和 函数用于设置和检查 密码。
以前版本的 Django ,如 0.90 版,只是简单使用 MD5 哈希,并且不使用盐值。为了向后 兼容,这些密码还是被支持的,指定用户的密码在第一次正常使用 后,会自动转换为新的 形式。
是一个 接口实现的类,区别如下:
在实践中,你一般不会自己使用 对象,但是 Web 请求会使用, 在下节会详述。
在 中添加 'django.contrib.auth' 后第一次运行 会提示你创建一个超级用户。如何在以后单独 创建可以使用以下命令行工具:
运行后会提供输入密码。输入密码后,用户会被立即创建。如果没有写明 或 ,会提示你输入这两个值。
如果你使用的是老版本的 Django ,则可以使用如下命令:
...这里的 /path/to 是你系统中 Django 代码所在路径。推荐使用 manage.py 命令,因为它为你提供了恰当的路径和环境。
如果要储存用户相关的额外信息, Django 提供了一个方法来定义一个站点相关模型,称 之为“用户概况( user profile )”。
具体实现的方式是定义一个用于储存额外信息或附加方法的模型,同时在模型中添加一个 OneToOneField 关联到 模型。这样就可以保证每个 只能创建唯一的模型实例。
为了使定义的模型成为站点的用户概况模型,必须有 设置中定义一个包含以下条目的字符串,条目间以点号分隔:
例如,如果用户概况模型名称为 UserProfile 且定义在一个名称为 accounts 的 应用中,那么正确的设置是:
当一个用户概况模型定义好并按上述方法指定之后,每个 对象就会有一个 方法,这个方法可以返回 与 相关联的用户概况模型。
如果用户概况不存在,则 方法不会创建用户概况。你必需在用户模型中为 信号注册一个句柄,并且在句柄中,如果 created=True ,则创建相关的用户概况。
更多信息,参见 .
到这里本文谈得是手动处理认证相关对象的低级 API ,在更高的级别 Django 可以把这个 认证框架挂接到 系统中。
首先,通过 设置安装 和 中间件。详见session documentation 。
安装完中间件后就可以在视图中操作 了。 会给你一个 对象来表现当前已登录的用户。如果用户 还没有登录,那么 会被设置为 一个 的实例(参见前一节)。 你可以用于 来区别 对待,如下例:
Django 在 模型中提供两个函数: 和 .
authenticate()用于验证指定用户的用户名和 密码。这个函数有两个关键字参数, username 和 password ,如果密码与 用户匹配则返回一个 对象。否则返回 None 。例如:
用于在视图中登录用户。它带有一个 对象和一个 对象。 使用 Django 的会话框架在会话中保存用户 的 ID 。因此如前文所述,会话中间件必须已经安装。
本例展示如何同时使用 和 :
首先调用 authenticate()
当你手动登录一个用户时, 必须 在调用 之前调用 。在成功验证用户后, 会在 上设置一个空属性(详见 ),这个信息在以后登录过程中要用到。
如果要通过比较明文密码和数据库中的哈希密码来手动认证用户,可以使用便捷的 函数。它带有两个参数:要 比较的明文密码和被比较的数据库中用户 密码 字段的完整值。如果匹配则返回 True ,否则返回 False 。
在视图中可以使用 来登出通过 登录的用户。它带有一个 对象,没有返回值。 例如:
注意当用户没有登录时,调用 函数不会 引发任何错误。
当调用 时,当前请求的会话数据会清空。 这是为了防止另一个用户使用同一个浏览器登录时会使用到前一个用户的会话数据。 如果要在用户登出后有会话中储存一些数据,那么得在 之后 储存。
当一个用户登录或登出时认证框架使用两个用于通知的 。
django.contrib.auth.signals.user_logged_in成功登录时所发的信号。
这个信号的参数:
sender 同上,登录成功的用户类。 request 当前 实例。 user 登录成功的用户实例。 django.contrib.auth.signals.user_logged_out调用登出方法时发的信号。
sender 同上:登出的用户类或者 None (用户验证失败时)。 request 当前 实例。 user 登出成功的用户实例或者 None (用户验证失败时)。页面限制的简单原始方式是检查 并且重定向到一个登录 页面:
...或显示一个出错页面:
做为一个捷径,可以使用方便的 饰件:
做以下事情:
缺省情况下,成功登录后重定向的路径是保存在 next 参数中的。如果你想用 另一个名称,那么 可以使用一个可选的 redirect_field_name 参数:
注意,如果你提供了一个值给 redirect_field_name ,那么你最好也同样自定义 你的登录模板。因为保存重定向路径的模板环境变量会使用 redirect_field_name 作为关键值,而不使用缺省的 "next" 。
还有一个可选的 login_url 参数。例如:
注意,如果你没有指定 login_url 参数,那么你需要为 映射适当的视图。例如,在缺省情况 下,在你的 URLconf 中加入以下一行:
以下是 django.contrib.auth.views.login 做了什么:
缺省情况下,在名为 registration/login.html 的模板中提供一个登录表单是 你的责任。这个模板要传递四个环境变量:
如果你不想调用模板文件 registration/login.html ,那么可以在 URLconf 通过额外参数把 template_name 传递给视图。例如,以下的 URLconf 设置会 使用 myapp/login.html 来代替缺省的模板:
你还可以通过传递 redirect_field_name 给视图来指定包含登录后重定向 URL 的 GET 字段的名称。这个字段缺省值为 next 。
这里有一个例子模板文件 registration/login.html ,你可以作为一个 基础。它假设你有一个 base.html 模板,模板中定义了一个 content 块:
如果你使用是其它认证方式(参见 ),那么可以 通过 authentication_form 参数把自定义的认证表单传递给登录视图。这个表单 必须在它的 __init__ 方法接受一个 request 关键字参数,并且提供一个 用于返回认证用户对象的 get_user 方法(这个方法只会在表单验证成功后 调用)。
除了 login() 视图外,认证系统还有其他一些有用的内建视图。这些 视图都在 中:
logout(request[, next_page, template_name, redirect_field_name])登出用户。
可选参数:
模板语境:
登出用户,并重定向到登录页面。
可选参数:
允许一个用户更改自己的密码。
可选参数:
template_name: 显示用户更改页面的模板全名。如果没有给出,则缺省为registration/password_change_form.html 。
post_change_redirect: 成功更改密码后要转向到的 URL 。
password_change_form: 一个自定义的“更改密码”的表单,必须接受一个 user 关键字参数。这个表单负责改变用户密码。
模板语境:
用户变更密码后要显示的页面。
可选参数:
生成一个用于用户重置密码的一次性链接,并通过电子邮件发送这个链接。
可选参数:
模板语境:
重置密码显示的页面。
可选参数:
重定向到登录页面,成功登录后回到另一个 URL 。
必选参数:
可选参数:
给出一个输入新密码的表单。
可选参数:
展现一个通知用户密码已成功变更的视图。
可选参数:
如果你不想使用内建的视图,但是想自己写表单,那么认证系统在 中提供了一些内建的表单:
class AdminPasswordChangeForm在管理接口中改变用户密码的表单。
class AuthenticationForm登录表单。
class PasswordChangeForm变更密码表单。
class PasswordResetForm生成重置密码链接并通过电子邮件发送链接的表单。
class SetPasswordForm不需要输入旧密码就可以变更用户密码的表单。
class UserChangeForm在管理接口中变更用户信息和权限的表单。
class UserCreationForm创建新用户的表单。
要基于一定的权限或测试来限制页面的操作,其本质上和上一节的内容相同。
最简单的方式是在社图中直接在 上直接运行你的测试。例如,这个视图调试用户是否 已登录并拥有polls.can_vote 权限:
作为一个捷径,你可使用方便的 user_passes_test 装饰件:
我们使用这个特定的测试作为一个相关的简单的例子。然而,如果你只要测试一个 用户是否有一个权限,那么你可以使用 装饰件,稍后 会谈到这个装饰件。
有一个必选参数: 带有一个 对象的可调用的函数,并且 如果用户被允许查看页面时返回 True 。注意 不自动检查 是否是匿名用户。
有一个可选的 login_url 参数,这个参数让你指定登录页面的 URL (缺省为 )。
例如:
检查用户是否有特定的权限是一个比较常见的任务。因此, Django 为此提供了一个 捷径: 装饰件。 使用这个装饰件,先前的例子可以写成:
至于 User.has_perm() 方法,权限名称以 "
注意 也有一个可选的 login_url 参数。例如:
在 login_required() 装饰件中 login_url 缺省为 。
要限制一个 操作,可以写一个视图的薄包装, 然后把你的 URLconf 指向薄包装而不是通用视图本身。例如:
Django 自带了一个简单的权限系统,它可以管理特定的用户和用户组的权限。
这个系统是用于 Django 管理站点的,但是欢迎在你自己的代码中使用它。
Django 管理站点使用如下权限:
权限是针对某种对象的全局性的设置,而不是针对某种对象的某个特定实例的。例如, 我们可以说“玛丽可以改写故事”,但是不能说“玛丽可以改写故事,但只限于她自己的 故事”,也不能说“玛丽只能改写符合特定条件(如出版日期、编号)的故事”。后面的 两种情况是 Django 的开发者正在讨论的东西,现在还不支持。
当 django.contrib.auth 存在于你的 设置中时,会 确保你安装的应用中每个 Django 模型在三个缺省的权限——增加、变更和删除。
当你运行 时这些权限会被创建。在向 增加 django.contrib.auth 后第一次运行 syncdb 时,会为以前已安装的模型和正在安装的模型创建缺省权限。以后每次运行 都会为所有模型创建缺省权限。
假设你的应用有一个名为 foo 的 和一个名为 Bar 的模型,要测试基本的权限可以这样:
使用 权限 可以创建自定义权限。
下例中的 Task 模型创建了三个自定义权限:
当运行 时以上代码只做了一件事:创建了三个 额外的权限。当用户尝试操作应用提供的功能(查看任务、改变任务状态和关闭任务) 时,你的代码必须负责检查权限的值。
和用户一样,权限也在 Django 模型中实现。
Permission 对象有以下字段:
Permission.name必需的。 50 个字符以下。例如: 'Can vote' 。
Permission.content_type属于的。一个 django_content_type 数据库表的引用,包含每一个已安装的 Django 模型。
Permission.codename必需的。 100 个字符以下。例如: 'Can_vote' 。
Permission 对象和其它 一样有标准数据操作方法。
当你使用 RequestContext 时,当前已登录的用户 及其权限在 中提供。
技术细节
在技术细节上,这些变量只有当你使用 RequestContext , 并且 你的 设置中包含"django.contrib.auth.context_processors.auth" 时才在模板环境中可用,这是 缺省 情况。更多见容,详见 。
当渲染一个模板 RequestContext 时,当前已登录 的用户(要么是一个 实例,要么是一个 实例)被储存在模板变量 {{ user }} 中:
如果一个 RequestContext 未使用则这个模板环境变量不可用。
当前已登录用户的权限储存在模板变量 {{ perms }} 中。这是一个django.contrib.auth.context_processors.PermWrapper 的实例。它是一个 模板友好的权限的代理。
在 {{ perms }} 对象中,单个属性的查找是 方法的一个代理。
如果已登录用户在 foo 应用中有权限,那么以下例子会显示 True
二维属性查找是 方法的一个代理。如果已登录用户有 foo.can_vote 权限,那么以下例子会显示 True
然而,你可以在模板 {% if %} 语句中检查权限:
组是一种把用户分类以便于分配权限或打上标签的常用方式。一个用户可以属于任意多个 组。
组中的用户自动被赋予这个组所拥有的权限。例如,如果 网站编辑 组拥有 can_edit_home_page 权限,那么这个组中的用户都有这个权限。
除了权限外,组还是一个把用户分类以便于打上标签或给予特定功能的便捷方式。例如, 你可创建一个 特别用户 组,然后就可以为这个编写特定的程序,给予站点特定的 部分的全员专有访问权限或发送会员专有电子邮件等等。
消息系统是一个指定用户的消息队列的轻度实现。
每个消息与一个 相关联。消息没有期限或 时间戳。
消息用于 Django 管理站点提示某个动作已成功完成。例如 "投票已成功创建" 就是 一个消息。
API 很简单:
models.User.message_set.create(message)使用 user_obj.message_set.create(message='message_text') 可以创建一个新 消息。
取得或删除消息使用user_obj.get_and_delete_messages()
在以下的例子视图中,系统为用户保存了创建节目单后产生的一个消息:
当你使用 RequestContext 时,当前的已登录用户 及其消息可由 中模板变量 {{ messages }} 来提供。下面就是显示消息的模板例子:
最后,注意消息框架只能用于用户数据库中已存在的用户。如果要向匿名用户发送消息, 请使用 。
Django 自带的认证系统对于一般情况已经够用了,但是你可能需要挂接其他的认证资源, 即另一个用户名和密码资源或认证方法。
例如,你的公司可能已有一个 LDAP ,其中已储存了每个员工的用户名和密码。如果在 LDAP 和 基于 Django 的应用中使用两套帐户,那么对于管理员和用户来说都是不方便 的。
因此,为了应对这种情况, Django 的认证系统可以让你挂接其他认证资源。你可以重载 Django 的缺省数据库计划或使缺省系统与其他系统协作。
关于 Django 认证后台处理的内容参见 。
在后台, Django 维护着一个用于认证的“认证后端”列表。当象前文 一节中提到的,使用 时, Django 会尝试列表中的所有 后端。如果第一个不行,则尝试第二个,以此类推,直到最后一个。
认证后端的列表中 设置中定义。设置内容应当是 一个指向可以用于认证的 Python 类的路径名的元组。这些类可以放在 Python 路径下的 任意位置。
缺省情况下, 被设置为:
这是检查 Django 用户数据库的基本认证方案。
的顺序是重要的,所以如果相同的用户名和密码在 多个后端中同时存在, Django 会在第一个匹配的地方停止。
Note
一旦一个用户通过认证, Django 会在会话中储存该用户所使用的后端,并且在随后 的认证中使用已储存的后端。即认证资源会被缓存,如果你改变 ,并且要强迫用户使用不同的后端重新认证, 那么需要清除会话数据。一个简单的方法是执行 Session.objects.all().delete()。
一个认证后端是一个执行两个方法的类: get_user(user_id) 和 authenticate(**credentials) 。
get_user 方法需要一个 user_id (可以是一个用户名、数据库 ID 或其他 东东),并且返回一个 用户 对象。
authenticate 方法有一个证书关键字参数。多数情况下看上去如下:
但是也可以认证一个标志,象下面这样:
不管怎样, authenticate 应当检查得到的材料。当材料有效时,返回一个符合条件 的 用户 对象,否则返回 None 。
Django 管理系统与本文开头所谈的 Django 用户 对象是紧密关联的。目前,最好的 办法是为你的认证后端中存在的每个用户创建一个 Django 用户 对象(例如,在你的 LDAP 目录中,你的 SQL 数据库中等等)。你要么写一个脚本,要么你的 认证 方法 在第一次用户登录时来创建 用户 对象。
以下是一个后端举例,它检查 settings.py 文件中定义的用户名和密码,并且在用户 第一次登录时创建一个 用户 对象:
自定义后端可以提供自己的权限。
用户模型会把权限查找函数 (:meth:~django.contrib.auth.models.User.get_group_permissions() 、 、 和 )委托给执行这些 函数的任何一个后端。
提供给用户的权限将会是所有后端返回的权限的超集。即 Django 会把任一个后端的权限 都提供给用户。
上面的简单的后端可以相当简单的执行神奇的权限管理:
上例中通过验证的用户得到了所有权限。注意,后端认证函数都把用户对象作为一个参数 并且接受与 同样的参数。
一个完整的认证实现参见 。通常情况下这是缺省 后端,查询的是 auth_permission表。
匿名用户是指没有经过认证的用户,如没有提供有效认证信息的用户。但是不等于说匿名 用户就什么事也不能做。多数网站可以允许匿名用户浏览大多数网页,而且很多网站还允许 匿名用户发表评论等。
Django 认证框架中没有地方储存匿名用户的权限,但是它允许自定义认证后端来处理匿名 用户的认证。这个能力在重用应用时尤其有用,这样不用设置就可以解决认证后端的所有 问题。
在你的认证后端中要启用匿名用户认证,必须把类属性 supports_anonymous_user 设置为 True 。(这样是为了认证后端的兼容性,确保 所有用户对象都是 类的真正实例。)这样, 就会把相应的权限方法委派给认证 后端。
在 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 。