The Django Book

Chapter 12: Sessions, Users, and Registration

十二章: Sessions, Users和 Registration

Its time for a confession: weve been deliberately ignoring an incredibly important aspect of Web development prior to this point. So far, weve thought of the traffic visiting our sites as some faceless, anonymous mass hurtling itself against our carefully designed pages.

是时候承认了:我们有意的避开了web开发中极其重要的方面。到目前为止,我们都在假定,网站流量是大量的匿名用户带来的。

This isnt true, of course. The browsers hitting our sites have real humans behind them (some of the time, at least). Thats a big thing to ignore: the Internet is at its best when it serves to connect people , not machines. If were going to develop truly compelling sites, eventually were going to have to deal with the bodies behind the browsers.

这当然不对,浏览器的背后都是活生生的人(至少某些时候是)。我们忽略了一件重要的事情:互联网服务于人而不是机器。要开发一个真正令人心动的网站,我们必须面对浏览器后面活生生的人。

Unfortunately, its not all that easy. HTTP is designed to be stateless that is, each and every request happens in a vacuum. Theres no persistence between one request and the next, and we cant count on any aspects of a request (IP address, user agent, etc.) to consistently indicate successive requests from the same person.

很不幸,这并不容易。HTTP被设计为”无状态”,每次请求都处于相同的空间中。在一次请求和下一次请求之间没有任何状态保持,我们无法根据请求的任何方面(IP地址,用户代理等)来识别来自同一人的连续请求。

In this chapter youll learn how to handle this lack of state. Well start at the lowest level (cookies ), and work up to the high-level tools for handling sessions, users and registration.

在本章中你将学会如何搞定状态的问题。好了,我们会从较低的层次(cookies)开始,然后过渡到用高层的工具来搞定会话,用户和注册的问题。

Cookies

Cookies

Browser developers long ago recognized that HTTPs statelessness poses a huge problem for Web developers, and thus cookies were born. A cookie is a small piece of information that browsers store on behalf of Web servers. Every time a browser requests a page from a certain server, it gives back the cookie that it initially received.

浏览器的开发者在很早的时候就已经意识到, HTTP’s 的无状态会对Web开发者带来很大的问题,于是(cookies)应运而生。cookies 是浏览器为 Web 服务器存储的一小段信息。每次浏览器从某个服务器请求页面时,它向服务器回送之前收到的cookies

Lets take a look how this might work. When you open your browser and type in google.com , your browser sends an HTTP request to Google that starts something like this:

来看看它是怎么工作的。当你打开浏览器并访问 google.com ,你的浏览器会给Google发送一个HTTP请求,起始部分就象这样:

GET / HTTP/1.1
Host: google.com
...

When Google replies, the HTTP response looks something like the following:

当 Google响应时,HTTP的响应是这样的:

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
            expires=Sun, 17-Jan-2038 19:14:07 GMT;
            path=/; domain=.google.com
Server: GWS/2.1
...

Notice the Set-Cookie header. Your browser will store that cookie value (PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671 ) and serve it back to Google every time you access the site. So the next time you access Google, your browser is going to send a request like this:

注意 Set-Cookie 的头部。你的浏览器会存储cookie值( PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671 ) ,而且每次访问google 站点都会回送这个cookie值。因此当你下次访问Google时,你的浏览器会发送像这样的请求:

GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
...

Google then can use that Cookie value to know that youre the same person who accessed the site earlier. This value might, for example, be a key into a database that stores user information. Google could (and does) use it to display your name on the page.

于是 Cookies 的值会告诉Google,你就是早些时候访问过Google网站的人。这个值可能是数据库中存储用户信息的key,可以用它在页面上显示你的用户名。

Getting and Setting Cookies

存取Cookies

When dealing with persistence in Django, most of the time youll want to use the higher-level session and/or user frameworks discussed a little later in this chapter. However, well pause and look at how to read and write cookies at a low level. This should help you understand how the rest of the tools discussed in the chapter actually work, and it will come in handy if you ever need to play with cookies directly.

在Django中处理持久化,大部分时候你会更愿意用高层些的session 和/或 后面要讨论的user 框架。但在此之前,我们需要停下来在底层看看如何读写cookies。这会帮助你理解本章节后面要讨论的工具是如何工作的,而且如果你需要自己操作cookies,这也会有所帮助。

Reading cookies that are already set is incredibly simple. Every request object has a COOKIES object that acts like a dictionary; you can use it to read any cookies that the browser has sent to the view:

读取已经设置好的cookies极其简单,每个request对象都有一个 COOKIES 对象,可以象使用字典般使用它,你可以读取任何浏览器发给视图(view)的任何cookies:

def show_color(request):
    if "favorite_color" in request.COOKIES:
        return HttpResponse("Your favorite color is %s" % \
            request.COOKIES["favorite_color"])
    else:
        return HttpResponse("You don't have a favorite color.")

Writing cookies is slightly more complicated. You need to use the set_cookie() method on an HttpResponse object. Heres an example that sets the favorite_color cookie based on a GET parameter:

写cookies稍微复杂点,需要用 HttpResponse 对象的 set_cookie() 方法来写。这儿有个基于 GET 参数来设置 favorite_color cookie的例子:

def set_color(request):
    if "favorite_color" in request.GET:

        # Create an HttpResponse object...
        response = HttpResponse("Your favorite color is now %s" % \
            request.GET["favorite_color"])

        # ... and set a cookie on the response
        response.set_cookie("favorite_color",
                            request.GET["favorite_color"])

        return response

    else:
        return HttpResponse("You didn't give a favorite color.")

You can also pass a number of optional arguments to response.set_cookie() that control aspects of the cookie, as shown in Table 12-1.

你可以给 response.set_cookie() 传递一些可选的参数来控制cookie的行为,详见表12-1。

Table 12-1: Cookie options
Parameter Default Description
max_age None Age (in seconds) that the cookie should last. If this parameter is None , the cookie will last only until the browser is closed.
expires None The actual date/time when the cookie should expire. It needs to be in the format "Wdy, DD-Mth-YY HH:MM:SS GMT" . If given, this parameter overrides the max_age parameter.
path "/"

The path prefix that this cookie is valid for. Browsers will only pass the cookie back to pages below this path prefix, so you can use this to prevent cookies from being sent to other sections of your site.

This is especially useful when you dont control the top level of your sites domain.

domain None

The domain that this cookie is valid for. You can use this parameter to set a cross-domain cookie. For example, domain=".example.com" will set a cookie that is readable by the domains www.example.com , www2.example.com , and an.other.sub.domain.example.com .

If this parameter is set to None , a cookie will only be readable by the domain that set it.

secure False If set to True , this parameter instructs the browser to only return this cookie to pages accessed over HTTPS.
表 12-1: Cookie 选项
参数 缺省值 描述
max_age None cookies的持续有效时间(以秒计),如果设置为 None cookies 在浏览器关闭的时候就失效了。
expires None cookies的过期时间,格式: "Wdy, DD-Mth-YY HH:MM:SS GMT" 如果设置这个参数,它将覆盖 max_age 参数。
path "/"

cookie生效的路径前缀,浏览器只会把cookie回传给带有该路径的页 面,这样你可以避免将cookie传给站点中的其他的应用。

当你的应用不处于站点顶层的时候,这个参数会非常有用。

domain None

cookie生效的站点。你可用这个参数来构造一个跨站cookie。如, domain=".example.com" 所构造的cookie对下面这些站点都是可 读的: www.example.comwww2.example.coman.other.sub.domain.example.com

如果该参数设置为 None ,cookie只能由设置它的站点读取。

secure False 如果设置为 True ,浏览器将通过HTTPS来回传cookie。

The Mixed Blessing of Cookies

好坏参半的Cookies

You might notice a number of potential problems with the way cookies work. Lets look at some of the more important ones:

也许你已经注意到了,cookies的工作方式可能导致的问题,一起来看看其中一些重要的方面:

Storage of cookies is essentially voluntary; browsers dont guarantee anything. In fact, all browsers enable users to control the policy for accepting cookies. If you want to see just how vital cookies are to the Web, try turning on your browsers prompt to accept every cookie option.

cookies存取完全是非强制性的,浏览器不保证这一点。事实上,所有的浏览器都让用户自己控制 是否接受cookies。如果你想知道cookies对于web应用有多重要,你可以试着打开这个浏览器的 选项:提示我接受每次cookie。

Despite their nearly universal use, cookies are still the definition of unreliability. This means that developers should check that a user actually accepts cookies before relying on them.

尽管cookies广为使用,但仍被认为是不可靠的的。这意味着,开发者使用cookies之前必须 检查用户是否可以接收cookie。

More important, you should never store important data in cookies. The Web is filled with horror stories of developers whove stored unrecoverable information in browser cookies only to have that data purged by the browser for one reason or another.

更重要的是,*永远* 也不要在cookie中存储重要的数据。开发者在cookie中存储了不可恢复 的数据,而浏览器却因为某种原因将cookie中的数据清得一干二净,这样令人发指的故事在 Web世界中比比皆是。

Cookies (especially those not sent over HTTPS) are not secure. Because HTTP data is sent in cleartext, cookies are extremely vulnerable to snooping attacks. That is, an attacker snooping on the wire can intercept a cookie and read it. This means you should never store sensitive information in a cookie.

Cookie(特别是那些没通过HTTPS传输的)是非常不安全的。因为HTTP数据是以明文发送的,所以 特别容易受到嗅探攻击。也就是说,嗅探攻击者可以在网络中拦截并读取cookies,因此你要 绝对避免在cookies中存储敏感信息。

Theres an even more insidious attack, known as a man-in-the-middle attack, wherein an attacker intercepts a cookie and uses it to pose as another user. Chapter 19 discusses attacks of this nature in depth, as well as ways to prevent it.

还有一种被称为”中间人”的攻击更阴险,攻击者拦截一个cookie并将其用于另一个用户。 第19章将深入讨论这种攻击的本质以及如何避免。

Cookies arent even secure from their intended recipients. Most browsers provide easy ways to edit the content of individual cookies, and resourceful users can always use tools like mechanize ( http://wwwsearch.sourceforge.net/mechanize/ ) to construct HTTP requests by hand.

即使从预想中的接收者返回的cookie也是不安全的,因为大多数浏览器都提供了很方便的方法来 修改cookies的内容,有技术背景的用户甚至可以用像mechanize ( http://wwwsearch.sourceforge.net/mechanize/ ) 这样的工具来手工构造HTTP请求。

So you cant store data in cookies that might be sensitive to tampering. The canonical mistake in this scenario is storing something like IsLoggedIn=1 in a cookie when a user logs in. Youd be amazed at the number of sites that make mistakes of this nature; it takes only a second to fool these sites security systems.

因此不能在cookies中存储可能会被篡改的敏感数据,“经典”错误是:在cookies中存储 IsLoggedIn=1 ,以标识用户已经登录。犯这类错误的站点数量多的令人难以置信; 绕过这些网站的安全系统也是易如反掌。

Djangos Session Framework

Django的 Session 框架

With all of these limitations and potential security holes, its obvious that cookies and persistent sessions are examples of those pain points in Web development. Of course, Djangos goal is to be an effective painkiller, so it comes with a session framework designed to smooth over these difficulties for you.

由于存在的限制与安全漏洞,cookies和持续性会话已经成为Web开发中令人头疼的典范。好消息是,Django的目标正是高效的“头疼杀手”,它自带的session框架会帮你搞定这些问题。

This session framework lets you store and retrieve arbitrary data on a per-site visitor basis. It stores data on the server side and abstracts the sending and receiving of cookies. Cookies use only a hashed session IDnot the data itselfthus protecting you from most of the common cookie problems.

你可以用session 框架来存取每个访问者任意数据,这些数据在服务器端存储,并用通过cookie来传输数据摘要。cookies只存储数据的哈希会话ID,而不是数据本身,从而避免了大部分的常见cookie问题。

Lets look at how to enable sessions and use them in views.

下面我们来看看如何打开session功能,并在视图中使用它。

Enabling Sessions

打开 Sessions功能

Sessions are implemented via a piece of middleware (see Chapter 15) and a Django model. To enable sessions, youll need to follow these steps:

Sessions 功能是通过一个中间件(middleware)和一个模型(model)来实现的。要打开sessions功能,需要以下几步操作:

  1. Edit your MIDDLEWARE_CLASSES setting and make sure MIDDLEWARE_CLASSES contains 'django.contrib.sessions.middleware.SessionMiddleware' .

  1. 编辑 MIDDLEWARE_CLASSES 配置,确保 MIDDLEWARE_CLASSES 中包含 'django.contrib.sessions.middleware.SessionMiddleware'

  1. Make sure 'django.contrib.sessions' is in your INSTALLED_APPS setting (and run manage.py syncdb if you have to add it).

  1. 确认 INSTALLED_APPS 中有 'django.contrib.sessions' (如果你是刚打开这个应用,别忘了运行 manage.py syncdb )

The default skeleton settings created by startproject have both of these bits already installed, so unless youve removed them, you probably dont have to change anything to get sessions to work.

如果项目是用 startproject 来创建的,配置文件中都已经安装了这些东西,除非你自己删除,正常情况下,你无需任何设置就可以使用session功能。

If you dont want to use sessions, you might want to remove the SessionMiddleware line from MIDDLEWARE_CLASSES and 'django.contrib.sessions' from your INSTALLED_APPS . It will save you only a small amount of overhead, but every little bit counts.

如果不需要session功能,你可以删除 MIDDLEWARE_CLASSES 设置中的 SessionMiddlewareINSTALLED_APPS 设置中的 'django.contrib.sessions' 。虽然这只会节省很少的开销,但积少成多啊。

Using Sessions in Views

在视图中使用Session

When SessionMiddleware is activated, each HttpRequest objectthe first argument to any Django view functionwill have a session attribute, which is a dictionary-like object. You can read it and write to it in the same way youd use a normal dictionary. For example, in a view you could do stuff like this:

SessionMiddleware 激活后,每个传给视图(view)函数的第一个参数``HttpRequest`` 对象都有一个 session 属性,这是一个字典型的对象。你可以象用普通字典一样来用它。例如,在视图(view)中你可以这样用:

# Set a session value:
request.session["fav_color"] = "blue"

# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]

# Clear an item from the session:
del request.session["fav_color"]

# Check if the session has a given key:
if "fav_color" in request.session:
    ...

You can also use other mapping methods like keys() and items() on request.session .

其他的映射方法,如 keys()items()request.session 同样有效:

There are a couple of simple rules for using Djangos sessions effectively:

下面是一些有效使用Django sessions的简单规则:

  • Use normal Python strings as dictionary keys on request.session (as opposed to integers, objects, etc.). This is more of a convention than a hard-and-fast rule, but its worth following.

  • 用正常的字符串作为key来访问字典 request.session , 而不是整数、对象或其它什么的。 这不是什么强硬的条规,但值得遵循。

  • Session dictionary keys that begin with an underscore are reserved for internal use by Django. In practice, the framework uses only a small number of underscore-prefixed session variables, but unless you know what they all are (and you are willing to keep up with any changes in Django itself), staying away from underscore prefixes will keep Django from interfering with your application.

  • Session字典中以下划线开头的key值是Django内部保留key值。框架只会用很少的几个下划线 开头的session变量,除非你知道他们的具体含义,而且愿意跟上Django的变化,否则,最好 不要用这些下划线开头的变量,它们会让Django搅乱你的应用。

  • Dont replace request.session with a new object, and dont access or set its attributes. Use it like a Python dictionary.

  • 不要用一个新对象来替换掉 request.session ,也不要存取其属性,象用普通Python字典一样用它。

Lets take a look at a few quick examples. This simplistic view sets a has_commented variable to True after a user posts a comment. Its a simple (but not particularly secure) way of preventing a user from posting more than one comment:

我们来看个简单的例子。这是个简单到不能再简单的例子:在用户发了一次评论后将 has_commented 设置为 True ,这是个简单(但不很安全)的、防止用户多次评论的方法。

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

This simplistic view logs in a member of the site:

下面是一个很简单的站点登录视图(view):

def login(request):
    try:
        m = Member.objects.get(username__exact=request.POST['username'])
        if m.password == request.POST['password']:
            request.session['member_id'] = m.id
            return HttpResponse("You're logged in.")
    except Member.DoesNotExist:
        return HttpResponse("Your username and password didn't match.")

And this one logs out a member, according to login() :

这是退出登录,根据 login() :

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

Note

注意

In practice, this is a lousy way of logging users in. The authentication framework discussed shortly handles this task for you in a much more robust and useful manner. These examples are deliberately simplistic so that you can easily see whats going on.

在实践中,这是很烂的用户登录方式,稍后讨论的认证(authentication )框架会帮你以更健壮和有利的方式来处理这些问题。这些非常简单的例子只是想让你知道这一切是如何工作的。

Setting Test Cookies

设置测试Cookies

As just mentioned, you cant rely on every browser accepting cookies. So, as a convenience, Django provides an easy way to test whether a users browser accepts cookies. You just need to call request.session.set_test_cookie() in a view, and check request.session.test_cookie_worked() in a subsequent viewnot in the same view call.

就像前面提到的,你不能指望所有的浏览器都可以接受cookie,因此,Django为了方便,也提供了检查用户浏览器是否接受cookie的简单方法。你只需在视图(view)中调用 request.session.set_test_cookie() ,并在后续的视图(view)、而不是当前的视图(view)中检查 request.session.test_cookie_worked()

This awkward split between set_test_cookie() and test_cookie_worked() is necessary due to the way cookies work. When you set a cookie, you cant actually tell whether a browser accepted it until the browsers next request.

虽然把 set_test_cookie()test_cookie_worked() 分开的做法看起来有些笨拙,但由于cookie的工作方式,这无可避免。当设置一个cookie时候,只能等浏览器下次访问的时候,你才能知道浏览器是否接受cookie。

Its good practice to use delete_test_cookie() to clean up after yourself. Do this after youve verified that the test cookie worked.

检查cookie是否可以正常工作后,你得自己用 delete_test_cookie() 来清除它,这是个好习惯。

Heres a typical usage example:

这是个典型例子:

def login(request):

    # If we submitted the form...
    if request.method == 'POST':

        # Check that the test cookie worked (we set it below):
        if request.session.test_cookie_worked():

            # The test cookie worked, so delete it.
            request.session.delete_test_cookie()

            # In practice, we'd need some logic to check username/password
            # here, but since this is an example...
            return HttpResponse("You're logged in.")

        # The test cookie failed, so display an error message. If this
        # was a real site we'd want to display a friendlier message.
        else:
            return HttpResponse("Please enable cookies and try again.")

    # If we didn't post, send the test cookie along with the login form.
    request.session.set_test_cookie()
    return render_to_response('foo/login_form.html')

Note

注意

Again, the built-in authentication functions handle this check for you.

再次强调,内置的认证函数会帮你做检查的。

Using Sessions Outside of Views

在视图(View)外使用Session

Internally, each session is just a normal Django model defined in django.contrib.sessions.models . Each session is identified by a more-or-less random 32-character hash stored in a cookie. Because its a normal model, you can access sessions using the normal Django database API:

从内部来看,每个session都只是一个普通的Django model(在 django.contrib.sessions.models 中定义)。每个session都由一个随机的32字节哈希串来标识,并存储于数据库中。由于这是一个普通的model,你可以用一般的Django 数据库API来读取session。

>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)

Youll need to call get_decoded() to get the actual session data. This is necessary because the dictionary is stored in an encoded format:

你得用 get_decoded() 来读取实际的session数据,因为session字典经过了编码存储。

>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}

When Sessions Are Saved

何时保存Session

By default, Django only saves to the database if the session has been modified that is, if any of its dictionary values have been assigned or deleted:

缺省的情况下,Django只会在session发生变化的时候才会存入数据库,比如说,字典赋值或删除。

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

To change this default behavior, set SESSION_SAVE_EVERY_REQUEST to True . If SESSION_SAVE_EVERY_REQUEST is True , Django will save the session to the database on every single request, even if it wasnt changed.

你可以设置 SESSION_SAVE_EVERY_REQUESTTrue 来改变这一缺省行为。如果 SESSION_SAVE_EVERY_REQUEST 设置为 True ,Django 会在每次请求的时候都把session存到数据库中,即使没有任何改变。

Note that the session cookie is sent only when a session has been created or modified. If SESSION_SAVE_EVERY_REQUEST is True , the session cookie will be sent on every request. Similarly, the expires part of a session cookie is updated each time the session cookie is sent.

注意,会话cookie只会在创建和修改的时候才会送出。但如果 SESSION_SAVE_EVERY_REQUEST 设置为 True ,会话cookie会在每次请求的时候都会送出。同时,每次会话cookie送出的时候,其 expires 参数都会更新。

Browser-Length Sessions vs. Persistent Sessions

浏览器关闭即失效会话 vs. 持久会话

You might have noticed that the cookie Google sent us contained expires=Sun, 17-Jan-2038 19:14:07 GMT; . Cookies can optionally contain an expiration date that advises the browser on when to remove the cookie. If a cookie doesnt contain an expiration value, the browser will expire it when the user closes his or her browser window. You can control the session frameworks behavior in this regard with the SESSION_EXPIRE_AT_BROWSER_CLOSE setting.

你可能注意到了,Google给我们发送的cookie中有 expires=Sun, 17-Jan-2038 19:14:07 GMT; cookie可以有过期时间,这样浏览器就知道什么时候可以删除cookie了。如果cookie没有设置过期时间,当用户关闭浏览器的时候,cookie就自动过期了。你可以改变 SESSION_EXPIRE_AT_BROWSER_CLOSE 的设置来控制session框架的这一行为。

By default, SESSION_EXPIRE_AT_BROWSER_CLOSE is set to False , which means session cookies will be stored in users browsers for SESSION_COOKIE_AGE seconds (which defaults to two weeks, or 1,209,600 seconds). Use this if you dont want people to have to log in every time they open a browser.

缺省情况下, SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 False ,这样,会话cookie可以在用户浏览器中保持有效达 SESSION_COOKIE_AGE 秒(缺省设置是两周,即1,209,600 秒)。如果你不想用户每次打开浏览器都必须重新登陆的话,用这个参数来帮你。

If SESSION_EXPIRE_AT_BROWSER_CLOSE is set to True , Django will use browser-length cookies.

如果 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 True ,当浏览器关闭时,Django会使cookie失效。

Other Session Settings

其他的Session设置

Besides the settings already mentioned, a few other settings influence how Djangos session framework uses cookies, as shown in Table 12-2.

除了上面提到的设置,还有一些其他的设置可以影响Django session框架如何使用cookie,详见表 12-2.

Table 12-2. Settings that influence cookiebehavior
Setting Description Default
SESSION_COOKIE_DOMAIN The domain to use for session cookies. Set this to a string such as ".lawrence.com" for cross-domain cookies, or use None for a standard cookie. None
SESSION_COOKIE_NAME The name of the cookie to use for sessions. This can be any string. "sessionid"
SESSION_COOKIE_SECURE Whether to use a secure cookie for the session cookie. If this is set to True , the cookie will be marked as secure, which means that browsers will ensure that the cookie is only sent via HTTPS. False
表 12-2. 影响cookie行为的设置
设置 描述 缺省值
SESSION_COOKIE_DOMAIN session cookie生效的站点,跨站点生效的 cookie可以这样设置:``”.lawrence.com”`` None 为标准cookie None
SESSION_COOKIE_NAME 用于session 的cookie名称,可以是任何 字符串 "sessionid"
SESSION_COOKIE_SECURE 是否在session中使用安全cookie,如果设置 True , cookie就会标记为安全, 这意味着cookie只会通过HTTPS来传输 False

Technical Details

技术细节

For the curious, here are a few technical notes about the inner workings of the session framework:

如果你还是好奇的话,下面是一些关于session框架内部工作方式的技术细节:

The session dictionary accepts any Python object capable of being pickled. See the documentation for Pythons built-in pickle module for information about how this works.

session 字典和普通Python对象一样,支持序列化,详见Python文档中内置 pickle 模块的部分。

Session data is stored in a database table named django_session .

Session 数据存在数据库表 django_session

Session data is fetched upon demand. If you never access request.session , Django wont hit that database table.

Session 数据在需要的时候才会读取,如果你从不使用 request.session , Django不会动相关数据库表的一根毛。

Django only sends a cookie if it needs to. If you dont set any session data, it wont send a session cookie (unless SESSION_SAVE_EVERY_REQUEST is set to True ).

Django 只在需要的时候才送出cookie。如果你压根儿就没有设置任何会话数据,它不会 送出会话cookie(除非 SESSION_SAVE_EVERY_REQUEST 设置为 True )

The Django sessions framework is entirely, and solely, cookie based. It does not fall back to putting session IDs in URLs as a last resort, as some other tools (PHP, JSP) do.

Django session 框架完全而且只能基于cookie,不会后退到把会话ID编码在URL中。(像某些工具(PHP,JSP)那样)

This is an intentional design decision. Putting sessions in URLs dont just make URLs ugly, but also make your site vulnerable to a certain form of session ID theft via the Referer header.

这是一个有意而为之的设计,把session放在URL中不只是难看,更重要的是这让你的站点 很容易受到攻击——通过 Referer header进行session ID”窃听”而实施的攻击。

If youre still curious, the source is pretty straightforward; look in django.contrib.sessions for more details.

如果你还是好奇,阅读源代码是最直接办法,详见 django.contrib.sessions

Users and Authentication

用户与Authentication

Were now halfway to linking browsers directly to Real People. Sessions give us a way of persisting data through multiple browser requests; the second part of the equation is using those sessions for user login. Of course, we cant just trust that users are who they say they are, so we need to authenticate them along the way.

现在,我们通过浏览器连接真实用户的目标已经完成一半了。通过session,我们可以在多次浏览器请求中保持数据, 接下来的部分就是用session来处理用户登录了。当然,不能仅凭用户的一面之词,我们就相信,所以我们需要认证。

Naturally, Django provides tools to handle this common task (and many others). Djangos user authentication system handles user accounts, groups, permissions, and cookie-based user sessions. This system is often referred to as an auth/auth (authentication and authorization) system. That name recognizes that dealing with users is often a two-step process. We need to

当然了,Django 也提供了工具来处理这样的常见任务(就像其他常见任务一样)。Django 用户认证系统处理用户帐号,组,权限以及基于cookie的用户会话。这个系统一般被称为 auth/auth (认证与授权)系统,这个系统的名称同时也表明了用户常见的两步处理。我们需要

  1. Verify (authenticate ) that a user is who he or she claims to be (usually by checking a username and password against a database of users)

  1. 验证 (认证) 用户是否是他所宣称的用户(一般通过查询数据库验证其用户名和密码)

  1. Verify that the user is authorized to perform some given operation (usually by checking against a table of permissions)

  1. 验证用户是否拥有执行某种操作的 授权 (通常会通过检查一个权限表来确认)

Following these needs, Djangos auth/auth system consists of a number of parts:

根据这些需求,Django 认证/授权 系统会包含以下的部分:

  • Users : People registered with your site

  • 用户 : 在网站注册的人

  • Permissions : Binary (yes/no) flags designating whether a user may perform a certain task

  • 权限 : 用于标识用户是否拥有某种操作的二进制(yes/no)标志

  • Groups : A generic way of applying labels and permissions to more than one user

  • :一种可以将标记和权限应用于多个用户的常用方法

  • Messages : A simple way to queue and display system messages to users

  • Messages : 向用户显示队列式的系统消息的常用方法

  • Profiles : A mechanism to extend the user object with custom fields

  • Profiles : 通过自定义字段扩展用户对象的机制

If youve used the admin tool (detailed in Chapter 6), youve already seen many of these tools, and if youve edited users or groups in the admin tool, youve actually been editing data in the auth systems database tables.

如果你已经用了admin工具(详见第6章),就会看见这些工具的大部分。如果已经用了admin工具来编辑用户和组,你实际上就已经在编辑认证系统中数据库表。

Enabling Authentication Support

打开认证支持

Like the session tools, authentication support is bundled as a Django application in django.contrib , which needs to be installed. Like the session system, its also installed by default, but if youve removed it, youll need to follow these steps to install it:

像session工具一样,认证支持也是一个Django应用,放在 django.contrib 中,所以也需要安装。与session系统相似,它也是缺省安装的,但如果它已经被删除了,通过以下步骤也能重新安装上:

  1. Make sure the session framework is installed as described earlier in this chapter. Keeping track of users obviously requires cookies, and thus builds on the session framework.

  1. 根据本章早前的部分确认已经安装了session 框架,需要确认用户使用cookie,这样sesson 框架才能正常使用。

  1. Put 'django.contrib.auth' in your INSTALLED_APPS setting and run manage.py syncdb .

  1. 'django.contrib.auth' 放在你的 INSTALLED_APPS 设置中,然后运行 manage.py syncdb

  1. Make sure that 'django.contrib.auth.middleware.AuthenticationMiddleware' is in your MIDDLEWARE_CLASSES setting*after* SessionMiddleware .

  1. 确认 SessionMiddleware 后面的 MIDDLEWARE_CLASSES 设置中包含 'django.contrib.auth.middleware.AuthenticationMiddleware'

With that installation out of the way, were ready to deal with users in view functions. The main interface youll use to access users within a view is request.user ; this is an object that represents the currently logged-in user. If the user isnt logged in, this will instead be an AnonymousUser object (see below for more details).

这样安装后,我们就可以在视图(view)的函数中用处理user了。在视图中存取users,主要用 request.user ;这个对象表示当前已登录的用户,如果用户还没登录,这就是一个 匿名 对象(细节见下)

You can easily tell if a user is logged in with the is_authenticated() method:

你可以很容易的通过 is_authenticated() 方法来判断一个用户是否已经登录了

if request.user.is_authenticated():
    # Do something for authenticated users.
else:
    # Do something for anonymous users.

Using Users

使用User对象

Once you have a User often from request.user , but possibly through one of the other methods discussed shortlyyou have a number of fields and methods available on that object. AnonymousUser objects emulate some of this interface, but not all of it, so you should always check user.is_authenticated() before assuming youre dealing with a bona fide user object. Tables 12-3 and 12-4 list the fields and methods, respectively, on User objects.

User 实例一般从 request.user ,或是其他下面即将要讨论到的方法取得,它有很多属性和方法。 AnonymousUser 对象模拟了 部分 的接口,但不是全部,在把它当成真正的user对象 使用前,你得检查一下 user.is_authenticated()

Table 12-3. Fields on User Objects
Field Description
username Required; 30 characters or fewer. Alphanumeric characters only (letters, digits, and underscores).
first_name Optional; 30 characters or fewer.
last_name Optional; 30 characters or fewer.
email Optional. Email address.
password Required. A hash of, and metadata about, the password (Django doesnt store the raw password). See the Passwords section for more about this value.
is_staff Boolean. Designates whether this user can access the admin site.
is_active Boolean. Designates whether this account can be used to log in. Set this flag to False instead of deleting accounts.
is_superuser Boolean. Designates that this user has all permissions without explicitly assigning them.
last_login A datetime of the users last login. This is set to the current date/time by default.
date_joined A datetime designating when the account was created. This is set to the current date/time by default when the account is created.
表 12-3. User 对象属性
属性 描述
username 必填; 少于等于30字符. 只允许字符,数字,下划线
first_name 可选; 少于等于30字符.
last_name 可选; 少于等于30字符.
email 可选. 邮件地址.
password 必填. 密码的摘要hash(Django不会存储原始密码),详见密码章节部分
is_staff 布尔值. 用户是否拥有网站的管理权限.
is_active 布尔值. 是否允许用户登录, 设置为``False``,可以不用删除用户来禁止 用户登录
is_superuser 布尔值. 用户是否拥有所有权限,而无需任何显式的权限分配定义
last_login 用户最后登录的时间,缺省会设置为当前时间
date_joined 创建用户的时间,当用户创建时,缺省的设置为当前的时间
Table 12-4. Methods on User Objects
Method Description
is_authenticated() Always returns True for real User objects. This is a way to tell if the user has been authenticated. This does not imply any permissions, and it doesnt check if the user is active. It only indicates that the user has sucessfully authenticated.
is_anonymous() Returns True only for AnonymousUser objects (and False for real User objects). Generally, you should prefer using is_authenticated() to this method.
get_full_name() Returns the first_name plus the last_name , with a space in between.
set_password(passwd) Sets the users password to the given raw string, taking care of the password hashing. This doesnt actually save the User object.
check_password(passwd) Returns True if the given raw string is the correct password for the user. This takes care of the password hashing in making the comparison.
get_group_permissions() Returns a list of permission strings that the user has through the groups he or she belongs to.
get_all_permissions() Returns a list of permission strings that the user has, both through group and user permissions.
has_perm(perm) Returns True if the user has the specified permission, where perm is in the format "package.codename" . If the user is inactive, this method will always return False .
has_perms(perm_list) Returns True if the user has all of the specified permissions. If the user is inactive, this method will always return False .
has_module_perms(app_label) Returns True if the user has any permissions in the given app_label . If the user is inactive, this method will always return False .
get_and_delete_messages() Returns a list of Message objects in the users queue and deletes the messages from the queue.
email_user(subj, msg) Sends an email to the user. This email is sent from the DEFAULT_FROM_EMAIL setting. You can also pass a third argument, from_email , to override the From address on the email.
get_profile() Returns a site-specific profile for this user. See the Profiles section for more on this method.
表 12-4. User 对象方法
方法 描述
is_authenticated() 如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。通过认证并不意味着 用户拥有任何权限,甚至也不检查该用户是否处于激活状 态,这只是表明用户成功的通过了认证。
is_anonymous() 如果是个 AnonymousUser ,返回值为 True , 如果是 User 对象,返回值为 False 。一般来 说, is_authenticated() 会比这个方法更常用些。
get_full_name() 返回值为: first_name 加上 last_name ,以 空格分隔。
set_password(passwd) 将用户的密码设置为给定的字符串,实际密码已被哈希 处理。这时并不会真正保存 User 对象。
check_password(passwd) 如果给定的字符串通过了密码检查,返回 True 。 密码比较已进行了哈希处理。
get_group_permissions() 返回用户通过所属组获得的权限列表
get_all_permissions() 返回用户通过所属组和用户自身权限所获得的所有权限 列表。
has_perm(perm) 如果用户拥有给定的权限,返回 Trueperm 应形如 "package.codename" 的格式。如果用户处于 非激活状态,则总是返回 False
has_perms(perm_list) 如果用户拥有所有给定的权限,返回 True 。 如果用户处于非激活状态,则总是返回 False
has_module_perms(app_label) 如果用户拥有任何给定 app_label 的权限,返回 True 。如果用户处于非激活状态,则总是返回 False
get_and_delete_messages() 返回用户的 Message 对象列表,并从队列中删除。
email_user(subj, msg) 给用户发送电子邮件,用 DEFAULT_FROM_EMAIL 的设 置作为发件人。也可以用第3个参数 from_email 来 覆盖设置。
get_profile() 返回用户的网站自定义profile,详见Profile章节

Finally, User objects have two many-to-many fields: groups and permissions . User objects can access their related objects in the same way as any other many-to-many field:

最后, User 对象有两个多对多的属性: groupspermissionsUser 对象可以 象使用其他多对多属性的方法一样使用它们。

# Set a user's groups:
myuser.groups = group_list

# Add a user to some groups:
myuser.groups.add(group1, group2,...)

# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)

# Remove a user from all groups:
myuser.groups.clear()

# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()

Logging In and Out

登录和退出

Django provides built-in view functions for handling logging in and out (and a few other nifty tricks), but before we get to those, lets take a look at how to log users in and out by hand. Django provides two functions to perform these actions in django.contrib.auth : authenticate() and login() .

Django 提供内置的视图(view)函数用于处理登录和退出 (以及其他奇技淫巧),但在开始前,我们来看看如何手工登录和退出,Django 在 django.contrib.auth 中提供了两个函数来处理这些事情—— authenticate()login()

To authenticate a given username and password, use authenticate() . It takes two keyword arguments, username and password , and it returns a User object if the password is valid for the given username. If the password is invalid, authenticate() returns None :

认证给出的用户名和密码,使用 authenticate() 函数。它接受两个参数,用户名 username 和 密码 password ,并在密码对用给出的用户名是合法的情况下返回一个 User 对象。当给出的密码不合法的时候 authenticate() 函数返回 None

>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
...     print "Correct!"
... else:
...     print "Oops, that's wrong!"

authenticate() only verifies a users credentials. To log in a user, use login() . It takes an HttpRequest object and a User object and saves the users ID in the session, using Djangos session framework.

authenticate() 只是验证一个用户的证书而已。而要登录一个用户,使用 login() 。该函数接受一个 HttpRequest 对象和一个 User 对象作为参数并使用Django的会话( session )框架把用户的ID保存在该会话中。

This example shows how you might use both authenticate() and login() within a view function:

下面的例子演示了如何在一个视图中同时使用 authenticate()login() 函数:

from django.contrib import auth

def login(request):
    username = request.POST['username']
    password = request.POST['password']
    user = auth.authenticate(username=username, password=password)
    if user is not None and user.is_active:
        # Correct password, and the user is marked "active"
        auth.login(request, user)
        # Redirect to a success page.
        return HttpResponseRedirect("/account/loggedin/")
    else:
        # Show an error page
        return HttpResponseRedirect("/account/invalid/")

To log out a user, use django.contrib.auth.logout() within your view. It takes an HttpRequest object and has no return value:

注销一个用户,在你的视图中使用 django.contrib.auth.logout() 。该函数接受一个 HttpRequest 对象作为参数,没有返回值。

from django.contrib import auth

def logout(request):
    auth.logout(request)
    # Redirect to a success page.
    return HttpResponseRedirect("/account/loggedout/")

Note that logout() doesnt throw any errors if the user wasnt logged in.

注意,即使用户没有登录, logout() 也不会抛出任何异常。

In practice, you usually will not need to write your own login/logout functions; the authentication system comes with a set of views for generically handling logging in and out.

在实际中,你一般不需要自己写登录/登出的函数;认证系统提供了一系例视图用来处理登录和登出。

The first step in using the authentication views is to wire them up in your URLconf. Youll need to add this snippet:

使用认证视图的第一步是把它们写在你的URLconf中。 你需要这样写:

from django.contrib.auth.views import login, logout

urlpatterns = patterns('',
    # existing patterns here...
    (r'^accounts/login/$',  login),
    (r'^accounts/logout/$', logout),
)

/accounts/login/ and /accounts/logout/ are the default URLs that Django uses for these views.

/accounts/login//accounts/logout/ 是Django提供的视图的默认URL。

By default, the login view renders a template at registration/login.html (you can change this template name by passing an extra view argument ,``template_name``). This form needs to contain a username and a password field. A simple template might look like this:

缺省情况下, login 视图渲染 registragiton/login.html 模板(可以通过视图的额外参数 template_name 修改这个模板名称)。这个表单必须包含 usernamepassword 域。如下示例:

{% extends "base.html" %}

{% block content %}

  {% if form.errors %}
    <p class="error">Sorry, that's not a valid username or password</p>
  {% endif %}

  <form action='.' method='post'>
    <label for="username">User name:</label>
    <input type="text" name="username" value="" id="username">
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">

    <input type="submit" value="login" />
    <input type="hidden" name="next" value="{{ next|escape }}" />
  <form action='.' method='post'>

{% endblock %}

If the user successfully logs in, he or she will be redirected to /accounts/profile/ by default. You can override this by providing a hidden field called next with the URL to redirect to after logging in. You can also pass this value as a GET parameter to the login view and it will be automatically added to the context as a variable called next that you can insert into that hidden field.

如果用户登录成功,缺省会重定向到 /accounts/profile 。表单中有一个hidden字段叫 next ,可以用在登录后指定url。也可以把这个值(指定的url)作为 GET 参数传递给login视图,这个参数会成为Context中名为 next 的变量,你可以把这个变量设置给表单中对应的隐含字段。

The logout view works a little differently. By default it renders a template at registration/logged_out.html (which usually contains a Youve successfully logged out message). However, you can call the view with an extra argument, next_page , which will instruct the view to redirect after a logout.

logout视图有一些不同。缺省的它渲染 registration/logged_out.html 模板(这个视图一般包含你已经成功退出的信息)。视图中还可以包含一个参数 next_page 用于退出后重定向。

Limiting Access to Logged-in Users

限制已登录用户的访问

Of course, the reason were going through all this trouble is so we can limit access to parts of our site.

有很多原因需要控制用户访问站点的某部分。

The simple, raw way to limit access to pages is to check request.user.is_authenticated() and redirect to a login page:

一个简单原始的限制方法是检查 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)
    # ...

or perhaps display an error message:

或者显示一个出错信息:

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    # ...

As a shortcut, you can use the convenient login_required decorator:

作为一个快捷方式, 你可以使用便捷的 login_required 修饰符:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...

login_required does the following:

login_required 做下面的事情:

  • If the user isnt logged in, redirect to /accounts/login/ , passing the current absolute URL in the query string as next , for example: /accounts/login/?next=/polls/3/ .

  • 如果用户没有登录, 重定向到 /accounts/login/ , 把当前绝对URL作为 next 在查询字符串中传递过去, 例如: /accounts/login/?next=/polls/3/ .

  • If the user is logged in, execute the view normally. The view code can then assume that the user is logged in.

  • 如果用户已经登录, 正常地执行视图函数. 视图代码就可以假定用户已经登录了.

Limiting Access to Users Who Pass a Test

对通过测试的用户限制访问

Limiting access based on certain permissions or some other test, or providing a different location for the login view works essentially the same way.

限制访问可以基于某种权限,某些检查或者为login视图提供不同的位置,这些实现方式大致相同

The raw way is to run your test on request.user in the view directly. For example, this view checks to make sure the user is logged in and has the permission polls.can_vote (more about how permissions works follows):

一般的方法是直接在视图的 request.user 上运行检查。例如,下面视图检查用户登陆并是否有 polls.can_vote 的权限:

def vote(request):
    if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        # vote here
    else:
        return HttpResponse("You can't vote in this poll.")

Again, Django provides a shortcut called user_passes_test . It takes arguments and generates a specialized decorator for your particular situation:

并且Django有一个称为 user_passes_test 的简洁方式。它根据情况使用参数并且产生特殊装饰符。

def user_can_vote(user):
    return user.is_authenticated() and user.has_perm("polls.can_vote")

@user_passes_text(user_can_vote, login_url="/login/")
def vote(request):
    # Code here can assume a logged-in user with the correct permission.
    ...

user_passes_test takes one required argument: a callable that takes a User object and returns True if the user is allowed to view the page. Note that user_passes_test does not automatically check that the User is authenticated; you should do that yourself.

user_passes_test 使用一个必需的参数:一个可调用的方法,当存在 User 对象并当此用户允许查看该页面时返回 True 。 注意 user_passes_test 不会自动检查 User 是否认证,你应该自己做这件事。

In this example were also showing the second optional argument, login_url , which lets you specify the URL for your login page (/accounts/login/ by default).

例子中我们也展示了第二个可选的参数 login_url ,它让你指定你的登录页面的URL(默认为 /accounts/login/ )。

Since its a relatively common task to check whether a user has a particular permission, Django provides a shortcut for that case: the permission_required() decorator. Using this decorator, the earlier example can be written as follows:

既然检查用户是否有一个特殊权限是相对常见的任务,Django为这种情形提供了一个捷径: permission_required() 装饰器 使用这个装饰器,前面的例子可以这样写:

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
    # ...

Note that permission_required() also takes an optional login_url parameter, which also defaults to '/accounts/login/' .

注意, permission_required() 也有一个可选的 login_url 参数, 这个参数默认为 '/accounts/login/'

Limiting Access to Generic Views

限制通用视图的访问

One of the most frequently asked questions on the Django users list deals with limiting access to a generic view. To pull this off, youll need to write a thin wrapper around the view and point your URLconf to your wrapper instead of the generic view itself:

在Django用户邮件列表中问到最多的问题是关于对通用视图的限制性访问。为实现这个功能,你需要自己包装视图,并且在URLconf中,将你自己的版本替换通用视图:

from dango.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail

@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

You can, of course, replace login_required with any of the other limiting decorators.

当然, 你可以用任何其他限定修饰符来替换 login_required

Managing Users, Permissions, and Groups

管理 Users, Permissions 和 Groups

The easiest way by far to manage the auth system is through the admin interface. Chapter 6 discusses how to use Djangos admin interface to edit users and control their permissions and access, and most of the time youll just use that interface.

管理认证系统最简单的方法是通过管理界面。 第六章讨论了怎样使用Django的管理界面来编辑用户和控制他们的权限和可访问性,并且大多数时间你都会只使用这个界面。

However, there are low-level APIs you can delve into when you need absolute control, and we discuss these in the sections that follow.

然而,当你需要绝对的控制权的时候,有一些低层 API 需要深入专研,我们将在下面的章节中讨论它们。

Creating Users
创建用户

Create users with the create_user helper function:

使用 create_user 辅助函数创建用户:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
...                                 email='jlennon@beatles.com',
...                                 password='glass onion')

At this point, user is a User instance ready to be saved to the database (create_user() doesnt actually call save() itself). You can continue to change its attributes before saving, too:

在这里, userUser 类的一个实例,准备用于向数据库中存储数据。 create_user() 函数并没有在数据库中创建记录,在保存数据之前,你仍然可以继续修改它的属性值。

>>> user.is_staff = True
>>> user.save()
Changing Passwords
修改密码

You can change a password with set_password() :

你可以使用 set_password() 来修改密码:

>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()

Dont set the password attribute directly unless you know what youre doing. The password is actually stored as a salted hash and thus cant be edited directly.

除非你清楚的知道自己在做什么,否则不要直接修改 password 属性。其中保存的是密码的 加入salt的hash值 ,所以不能直接编辑。

More formally, the password attribute of a User object is a string in this format:

一般来说, User 对象的 password 属性是一个字符串,格式如下:

hashtype$salt$hash

Thats a hash type, the salt, and the hash itself, separated by the dollar sign ($) character.

这是哈希类型,salt和哈希本身,用美元符号($)分隔。

hashtype is either sha1 (default) or md5 , the algorithm used to perform a one-way hash of the password. salt is a random string used to salt the raw password to create the hash, for example:

hashtypesha1 (默认)或者 md5 ,它是用来处理单向密码哈希的算法,Salt是一个用来加密原始密码来创建哈希的随机字符串,例如:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

The User.set_password() and User.check_password() functions handle the setting and checking of these values behind the scenes.

User.set_password()User.check_password() 函数在后台处理和检查这些值。

Is a Salted Hash Some Kind of Drug?

一个加入salt的哈希算法是某种毒品吗?

No, a salted hash has nothing to do with marijuana; its actually a common way to securely store passwords. A hash is a one-way cryptographic functionthat is, you can easily compute the hash of a given value, but its nearly impossible to take a hash and reconstruct the original value.

不是,一个 加入salt值的哈希算法 可不是什么毒品;事实上它是一种确保密码存储安全的常用方法。一次 哈希 是一次单向的加密过程,你能容易地计算出一个给定值的哈希码,但是几乎不可能从一个哈希码解出它的原值。

If we stored passwords as plain text, anyone who got their hands on the password database would instantly know everyones password. Storing passwords as hashes reduces the value of a compromised database.

如果我们以普通文本存储密码,任何能进入数据库的人都能轻易的获取每个人的密码。使用哈希方式来存储密码相应的减少了数据库泄露密码的可能。

However, an attacker with the password database could still run a brute- force attack, hashing millions of passwords and comparing those hashes against the stored values. This takes some time, but less than you might thinkcomputers are incredibly fast.

然而,攻击者仍然可以使用 暴力破解 使用上百万个密码与存储的值对比来获取数据库密码,这需要花一些时间,但是智能电脑惊人的速度超出了你的想象

Worse, there are publicly available rainbow tables , or databases of precomputed hashes of millions of passwords. With a rainbow table, an attacker can break most passwords in seconds.

更糟糕的是我们可以公开地得到 rainbow tables (一种暴力密码破解表)或预备有上百万哈希密码值的数据库。使用rainbow tables可以在几秒之内就能搞定最复杂的一个密码。

Adding a salt basically an initial random valueto the stored hash adds another layer of difficulty to breaking passwords. Since salts differ from password to password, they also prevent the use of a rainbow table, thus forcing attackers to fall back on a brute-force attack, itself made more difficult by the extra entropy added to the hash by the salt.

在存储的hash值的基础上,加入 salt 值(一个随机值),增加了密码的强度,使得破解更加困难。因为每个密码的salt值都不相同,这也限制了rainbow table的使用,使得攻击者只能使用最原始的暴力破解方法。而加入的salt值使得hash的熵进一步获得增加,使得暴力破解的难度又进一步加大。

While salted hashes arent absolutely the most secure way of storing passwords, theyre a good middle ground between security and convenience.

加入salt值得hash并不是绝对安全的存储密码的方法,然而在安全和方便之间有很大的中间地带需要我们来做决定。

Handling Registration

处理注册

We can use these low-level tools to create views that allow users to sign up. Nearly every developer wants to implement registration differently, so Django leaves writing a registration view up to you. Luckily, its pretty easy.

我们可以使用这些底层工具来创建允许用户注册的视图。最近每个开发人员都希望实现各自不同的注册方法,所以Django把写一个注册视图的工作留给了你。幸运的是,这很容易。

At its simplest, we could provide a small view that prompts for the required user information and creates those users. Django provides a built-in form you can use for this purpose, which well use in this example:

作为这个事情的最简化处理, 我们可以提供一个小视图, 提示一些必须的用户信息并创建这些用户. Django为此提供了可用的内置表单, 在下面这个例子中很好地使用了:

from django import oldforms as forms
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth.forms import UserCreationForm

def register(request):
    form = UserCreationForm()

    if request.method == 'POST':
        data = request.POST.copy()
        errors = form.get_validation_errors(data)
        if not errors:
            new_user = form.save(data)
            return HttpResponseRedirect("/books/")
    else:
        data, errors = {}, {}

    return render_to_response("registration/register.html", {
        'form' : forms.FormWrapper(form, data, errors)
    })

This form assumes a template named registration/register.html . Heres an example of what that template might look like:

这个表单构想了一个叫 registration/register.html 的模板. 这里是一个这个模板的可能的样子的例子:

{% extends "base.html" %}

{% block title %}Create an account{% endblock %}

{% block content %}
  <h1>Create an account</h1>
  <form action="." method="post">
    {% if form.error_dict %}
      <p class="error">Please correct the errors below.</p>
    {% endif %}

    {% if form.username.errors %}
      {{ form.username.html_error_list }}
    {% endif %}
    <label for="id_username">Username:</label> {{ form.username }}

    {% if form.password1.errors %}
      {{ form.password1.html_error_list }}
    {% endif %}
    <label for="id_password1">Password: {{ form.password1 }}

    {% if form.password2.errors %}
      {{ form.password2.html_error_list }}
    {% endif %}
    <label for="id_password2">Password (again): {{ form.password2 }}

    <input type="submit" value="Create the account" />
  </label>
{% endblock %}

Note

备注

django.contrib.auth.forms.UserCreationForm is, at the time of publication, an oldforms Form. See http://www.djangoproject.com/documentation/0.96/forms/ for details on oldforms. The transition to newforms, as covered in Chapter 7, will be completed in the near future.

在本书出版之时, django.contrib.auth.forms.UserCreationForm 是一个 oldforms 表单. 参看 http://www.djangoproject.com/documentation/0.96/forms/ 可以获取有关 oldforms 的详细信息. 转换到有关 newforms 的内容在第7章中将会讲述, newforms 功能 将会在不远的将来完成.

Using Authentication Data in Templates

在模板中使用认证数据

The current logged-in user and his or her permissions are made available in the template context when you use RequestContext (see Chapter 10).

当前登入的用户以及他(她)的权限可以通过 RequestContext 在模板的context中使用(详见第10章)。

Note

备注

Technically, these variables are only made available in the template context if you use RequestContext and your TEMPLATE_CONTEXT_PROCESSORS setting contains "django.core.context_processors.auth" , which is the default. Again, see Chapter 10 for more information.

从技术上来说,只有当你使用了 RequestContext 并且 TEMPLATE_CONTEXT_PROCESSORS 设置包含了 "django.core.context_processors.auth" (默认情况就是如此)时,这些变量才能在模板context中使用。更详细的内容,也请参考第10章。

When using RequestContext , the current user (either a User instance or an AnonymousUser instance) is stored in the template variable {{ user }} :

当使用 RequestContext 时, 当前用户 (是一个 User 实例或一个 AnonymousUser 实例) 存储在模板变量 {{ user }} 中:

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

This users permissions are stored in the template variable {{ perms }} . This is a template-friendly proxy to a couple of permission methods described shortly.

这些用户的权限信息存储在 {{ perms }} 模板变量中。这是一个在模板中使用很方便的代理,其中包含一些权限相关函数的简写。

There are two ways you can use this perms object. You can use something like {{ perms.polls }} to check if the user has any permissions for some given application, or you can use something like {{ perms.polls.can_vote }} to check if the user has a specific permission.

你有两种方式来使用 perms 对象。你可以使用类似于 {{ perms.polls }} 的形式来检查,对于某个特定的应用,一个用户是否具有 任意 权限;你也可以使用 {{ perms.polls.can_vote }} 这样的形式,来检查一个用户是否拥有特定的权限。

Thus, you can check permissions in template {% if %} statements:

这样你就可以在模板中的 {% if %} 语句中检查权限:

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

The Other Bits: Permissions, Groups, Messages, and Profiles

其他一些功能:权限,组,消息和档案

There are a few other bits of the authentication framework that weve only dealt with in passing. Well take a closer look at them in the following sections.

在认证框架中还有其他的一些功能。我们会在接下来的几个部分中进一步地了解它们。

Permissions

权限

Permissions are a simple way to mark users and groups as being able to perform some action. They are usually used by the Django admin site, but you can easily use them in your own code.

权限可以很方便地标识用户和用户组可以执行的操作。它们被Django的admin管理站点所使用,你也可以在你自己的代码中使用它们。

The Django admin site uses permissions as follows:

Django的admin站点如下使用权限:

  • Access to view the add form, and add an object is limited to users with the add permission for that type of object.

  • 只有设置了 add 权限的用户才能使用添加表单,添加对象的视图。

  • Access to view the change list, view the change form, and change an object is limited to users with the change permission for that type of object.

  • 只有设置了 change 权限的用户才能使用变更列表,变更表格,变更对象的视图。

  • Access to delete an object is limited to users with the delete permission for that type of object.

  • 只有设置了 delete 权限的用户才能删除一个对象。

Permissions are set globally per type of object, not per specific object instance. For example, its possible to say Mary may change news stories, but its not currently possible to say Mary may change news stories, but only the ones she created herself or Mary may only change news stories that have a certain status, publication date, or ID.

权限是根据每一个类型的对象而设置的,并不具体到对象的特定实例。例如,我们可以允许Mary改变新故事,但是目前还不允许设置Mary只能改变自己创建的新故事,或者根据给定的状态,出版日期或者ID号来选择权限。

These three basic permissionsadd, change, and deleteare automatically created for each Django model that has a class Admin . Behind the scenes, these permissions are added to the auth_permission database table when you run manage.py syncdb .

这三个基本权限:添加,变更和删除,会被自动添加到所有的Django模型中,只要该模型包含 class Admin 。当你执行 manage.py syncdb 的时候,这些就被自动添加到 auth_permission 数据表中。

These permissions will be of the form "<app>.<action>_<object_name>" . That is, if you have a polls application with a Choice model, youll get permissions named "polls.add_choice" , "polls.change_choice" , and "polls.delete_choice" .

权限以 "<app>.<action>_<object_name>" 的形式出现。如果你有一个 polls 的应用,包含一个 Choice 模型,你就有以下三个权限,分别叫做 "polls.add_choice""polls.change_choice" ,和 "polls.delete_choice"

Note that if your model doesnt have class Admin set when you run syncdb , the permissions wont be created. If you initialize your database and add class Admin to models after the fact, youll need to run syncdb again to create any missing permissions for your installed applications.

注意,如果当你运行 syncdb 时,模型中没有包含 class Admin ,该模型对应的权限就不会被创建。如果你在初始化数据库以后,又在自己的模型中加入了 class Admin ,你就需要重新运行 syncdb 来为应用加入权限。

You can also create custom permissions for a given model object using the permissions attribute on Meta . This example model creates three custom permissions:

你也可以通过设置 Meta 中的 permissions 属性,来为给定的模型定制权限。下面的例子创建了三个自定义的权限:

class USCitizen(models.Model):
    # ...
    class Meta:
        permissions = (
            # Permission identifier     human-readable permission name
            ("can_drive",               "Can drive"),
            ("can_vote",                "Can vote in elections"),
            ("can_drink",               "Can drink alcohol"),
        )

This only creates those extra permissions when you run syncdb ; its up to you to check for these permissions in your views.

当你运行 syncdb 时,额外的权限才会被加入;你需要自己在视图中添加权限相关的代码。

Just like users, permissions are implemented in a Django model that lives in django.contrib.auth.models . This means that you can use Djangos database API to interact directly with permissions if you like.

就跟用户一样,权限也就是Django模型中的 django.contrib.auth.models 。因此如果你愿意,你也可以通过Django的数据库API直接操作权限。

Groups

Groups are a generic way of categorizing users so you can apply permissions, or some other label, to those users. A user can belong to any number of groups.

组提供了一种通用的方式来让你按照一定的权限规则和其他标签将用户分类。一个用户可以隶属于任何数量的组。

A user in a group automatically has the permissions granted to that group. For example, if the group Site editors has the permission can_edit_home_page , any user in that group will have that permission.

在一个组中的用户自动获得了赋予该组的权限。例如, Site editors 组拥有 can_edit_home_page 权限,任何在该组中的用户都拥有这个权限。

Groups are also a convenient way to categorize users to give them some label, or extended functionality. For example, you could create a group 'Special users' , and you could write code that could, say, give those users access to a members-only portion of your site, or send them members-only email messages.

组也可以通过给定一些用户特殊的标记,来扩展功能。例如,你创建了一个 'Special users' 组,并且允许组中的用户访问站点的一些VIP部分,或者发送VIP的邮件消息。

Like users, the easiest way to manage groups is through the admin interface. However, groups are also just Django models that live in django.contrib.auth.models , so once again you can always use Djangos database APIs to deal with groups at a low level.

和用户管理一样,admin接口是管理组的最简单的方法。然而,组也就是Django模型 django.contrib.auth.models ,因此你可以使用Django的数据库API,在底层访问这些组。

Messages

消息

The message system is a lightweight way to queue messages for given users. A message is associated with a User . Theres no concept of expiration or timestamps.

消息系统会为给定的用户接收消息。每个消息都和一个 User 相关联。其中没有超时或者时间戳的概念。

Messages are used by the Django admin interface after successful actions. For example, when you create an object, youll notice a The object was created successfully message at the top of the admin page.

在每个成功的操作以后,Django的admin管理接口就会使用消息机制。例如,当你创建了一个对象,你会在admin页面的顶上看到 The object was created successfully 的消息。

You can use the same API to queue and display messages in your own application. The API is simple:

你也可以使用相同的API在你自己的应用中排队接收和显示消息。API非常地简单:

  • To create a new message, use user.message_set.create(message='message_text') .

  • 要创建一条新的消息,使用 user.message_set.create(message='message_text')

  • To retrieve/delete messages, use user.get_and_delete_messages() , which returns a list of Message objects in the users queue (if any) and deletes the messages from the queue.

  • 要获得/删除消息,使用 user.get_and_delete_messages() ,这会返回一个 Message 对象的列表,并且从队列中删除返回的项。

In this example view, the system saves a message for the user after creating a playlist:

在例子视图中,系统在创建了播放单(playlist)以后,为用户保存了一条消息。

def create_playlist(request, songs):
    # Create the playlist with the given songs.
    # ...
    request.user.message_set.create(
        message="Your playlist was added successfully."
    )
    return render_to_response("playlists/create.html",
        context_instance=RequestContext(request))

When you use RequestContext , the current logged-in user and his or her messages are made available in the template context as the template variable {{ messages }} . Heres an example of template code that displays messages:

当使用 RequestContext ,当前登录的用户以及他(她)的消息,就会以模板变量 {{ messages }} 出现在模板的context中。下面是显示消息的一个例子模板代码:

{% if messages %}
<ul>
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

Note that RequestContext calls get_and_delete_messages behind the scenes, so any messages will be deleted even if you dont display them.

需要注意的是 RequestContext 会在后台调用 get_and_delete_messages ,因此即使你没有显示它们,它们也会被删除掉。

Finally, note that this messages framework only works with users in the user database. To send messages to anonymous users, use the session framework directly.

最后注意,这个消息框架只能服务于在用户数据库中存在的用户。如果要向匿名用户发送消息,请直接使用会话框架。

Profiles

档案

The final piece of the puzzle is the profile system. To understand what profiles are all about, lets first look at the problem.

最后一个难题是档案系统.为了理解什么是档案,让我们先看看问题.

In a nutshell, many sites need to store more user information than is available on the standard User object. To compound the problem, most sites will have different extra fields. Thus, Django provides a lightweight way of defining a profile object thats linked to a given user. This profile object can differ from project to project, and it can even handle different profiles for different sites served from the same database.

简单来说,许多网站需要存储比标准 User 对象更多的用户信息。为了解决这个问题,大多数网站都会有不同的额外字段。所以,Django提供一个轻量级的方式定义档案对象链接到指定的用户。这个档案对象在每个项目中可以是不同的,甚至可以为同一数据库服务的不同的站点处理不同的档案。

The first step in creating a profile is to define a model that holds the profile information. The only requirement Django places on this model is that it have a unique ForeignKey to the User model; this field must be named user . Other that that, you can use any other fields you like. Heres a strictly arbitrary profile model:

创建档案的第一步是定义一个模型(model)来存储档案信息。Django对这个模型所做的唯一的限制是,必须要包含唯一的一个对 User 模型的 ForeignKey ,而且这个字段必须要叫做 user 。其他的字段可以由你自己掌控。下面是一个档案模型的例子:

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

class MySiteProfile(models.Model):
    # This is the only required field
    user = models.ForeignKey(User, unique=True)

    # The rest is completely up to you...
    favorite_band = models.CharField(maxlength=100, blank=True)
    favorite_cheese = models.CharField(maxlength=100, blank=True)
    lucky_number = models.IntegerField()

Next, youll need to tell Django where to look for this profile object. You do that by setting the AUTH_PROFILE_MODULE setting to the identifier for your model. So, if your model lives in an application called myapp , youd put this in your settings file:

下一步,你需要告诉Django去哪里查找档案对象。你可以通过设置模型中的 AUTH_PROFILE_MODULE 变量达到这个目的。因此,如果你的模型包含在 myapp 这个应用中,你就需要如下编写你的设置文件:

AUTH_PROFILE_MODULE = "myapp.mysiteprofile"

Once thats done, you can access a users profile by calling user.get_profile() . This function could raise a SiteProfileNotAvailable exception if AUTH_PROFILE_MODULE isnt defined, or it could raise a DoesNotExist exception if the user doesnt have a profile already (youll usually catch that exception and create a new profile at that time).

一旦完成,你就可以通过调用 user.get_profile() 函数来获得用户档案。如果 AUTH_PROFILE_MODULE 变量没有设置,这个函数可能会抛出 SiteProfileNotAvailable 异常;如果这个用户不存在档案,也可能会抛出 DoesNotExist 异常(通常情况下,你会捕获这个异常并在当时创建一个新的档案)。

Whats Next

接下来?

Yes, the session and authorization system is a lot to absorb. Most of the time you wont need all the features described in this chapter, but when you need to allow complex interactions between users, its good to have all that power available.

是的,会话和认证系统有太多的东西要学。大多数情况下,你并不需要本章所提到的所有功能。然而当你需要允许用户之间复杂的互操作时,所有的功能都能使用就显得很重要了。

In the next chapter, well take a look at a piece of Django that builds on top of this session/user system: the comments application. It allows you to easily attach commentsfrom anonymous or authenticated usersto arbitrary objects. Onward and upward!

在下一章节中,我们会来深入了解Django建立在会话/用户系统之上的一个系统:评论应用。它允许你很方便地以匿名用户或者注册用户的身份,向任意类型的对象添加评论。让我们继续向前吧。

Copyright 2006 Adrian Holovaty and Jacob Kaplan-Moss.
This work is licensed under the GNU Free Document License.
Hosting graciously provided by media temple
Chinese translate hosting by py3k.cn. 粤ICP备16122281号-1