外部认证服务
第六章的例子已经展示了如何加密 cookies 以及通过 tornado.web.authenticated 修饰符去执行一些简单的用户验证表单.在这一章中,我们将会看到如何依靠第三方的web server 完成用户验证.一些流行的 web API 例如 facebook 和 twitter 使用 OAuth 协议,OAuth协议支持第三方应用在用户知情并且同意的情况下获取用户信息并以此做为用户身份验证的依据. tornado 提供了许多 python 的混合插件去帮助开发者完成这些第三方服务的验证,去支持那些流行的 web server 验证或使用标准的 OAuth 支持.在本章,我们将会通过两个例子深入探讨如何使用 tornado 的 auth 模块.一个是如何连接到 twitter ,另一个是如何连接到 facebook.
tornado 的 auth 模块
做为开发者,也许你想允许你的用户直接通过你的应用去推送更新信息到 twitter 上,或者获取 facebook 的状态.许多社交网络或者单点登录的 API 提供了一个标准的工作流程支持第三方应用的用户验证. tornado 的 auth 模块提供的类包括: OpenID, OAuth, OAuth2.0, twitter, friendfeed, google OpenID, facebook REST API, facebook Graph API.在你自己的应用中可以通过这些接口去完成一些特定的外部服务验证. tornado 的 auth 模块提供了很简洁的业务流程支持开发人员完成连接任意一个支持认证的服务器对应用的用户信息进行验证.
验证的业务流程
每一个公开的验证方法实现的业务流程都有细微的差别.但大部分都会开放 authorize_redirect 和 get_authenticated_user 的方法. authorize_redirect 方法将重定向未验证的用户到第三方认证服务的页面.在验证的页面,用户登录并授权你的应用关联到他的帐号之后,通常情况下,用户将返回你的应用并附加上一条极短的临时认证代码,通过调用 get_authenticated_user 方法去交换这条临时认证代码,并以此为依据将一个属于用户的长期认证信息颁发给用户,然后将认证重定向到这个长期认证上.这个特定的 authentication 类支持调用许多外部服务器的 API.例如 twitter, facebook, friendfeed, google 等公司都开发并提供了相应的用户验证服务器及调用的 API文档.
异步请求
一个特别需要注意的事情是, auth 模块使用了 tornado 的异步 HTTP 请求进行支持.正如我们在第五章看到的,异步的 HTTP 请求允许 tornado 去处理请求并在请求外部服务没有得到响应时候接受其它新的请求.
我们再通过一个例子简单地回顾一下如何使用异步的请求吧,每一个需要使用异步处理的方法前面都需要使用 @tornado.web.asynchronous 修饰符.
例子:登录到twitter
让我们通过一个例子来学习使用 twitter API 完成用户验证吧.这个应用将会把未登录的用户重定向到 twitter 的验证页面.它会提示用户使用的用户名和密码信息.完成 twitter 验证之后,用户会再次被重定向到你在 twitter 的应用配置页面指定的 URL上.
首先你必须到 twitter 上注册一个新的应用. 在你登录twitter developers site后,如果你没有创建任何 app ,就可以看到一个”create an app”的连接.一旦你创建了你的 twitter 应用.将会给你分配一个 关联的 token 并颁发一个认证允许你的应用连接 twitter.你必须用这个认证的值去替换你应用中某些代码段的值.
现在,让我们来看看例子7-1的代码
例子7-1.twitter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
import
tornado
.
web
import
tornado
.
httpserver
import
tornado
.
auth
import
tornado
.
ioloop
class
TwitterHandler
(
tornado
.
web
.
RequestHandler
,
tornado
.
auth
.
TwitterMixin
)
:
@
tornado
.
web
.
asynchronous
def
get
(
self
)
:
oAuthToken
=
self
.
get_secure_cookie
(
'oauth_token'
)
oAuthSecret
=
self
.
get_secure_cookie
(
'oauth_secret'
)
userID
=
self
.
get_secure_cookie
(
'user_id'
)
if
self
.
get_argument
(
'oauth_token'
,
None
)
:
self
.
get_authenticated_user
(
self
.
async_callback
(
self
.
_twitter_on_auth
)
)
return
elif
oAuthToken
and
oAuthSecret
:
accessToken
=
{
'key'
:
oAuthToken
,
'secret'
:
oAuthSecret
}
self
.
twitter_request
(
'/users/show'
,
access_token
=
accessToken
,
user_id
=
userID
,
callback
=
self
.
async_callback
(
self
.
_twitter_on_user
)
)
return
self
.
authorize_redirect
(
)
def
_twitter_on_auth
(
self
,
user
)
:
if
not
user
:
self
.
clear_all_cookies
(
)
raise
tornado
.
web
.
HTTPError
(
500
,
'Twitter authentication failed'
)
self
.
set_secure_cookie
(
'user_id'
,
str
(
user
[
'id'
]
)
)
self
.
set_secure_cookie
(
'oauth_token'
,
user
[
'access_token'
]
[
'key'
]
)
self
.
set_secure_cookie
(
'oauth_secret'
,
user
[
'access_token'
]
[
'secret'
]
)
self
.
redirect
(
'/'
)
def
_twitter_on_user
(
self
,
user
)
:
if
not
user
:
self
.
clear_all_cookies
(
)
raise
tornado
.
web
.
HTTPError
(
500
,
"Couldn't retrieve user information"
)
self
.
render
(
'home.html'
,
user
=
user
)
class
LogoutHandler
(
tornado
.
web
.
RequestHandler
)
:
def
get
(
self
)
:
self
.
clear_all_cookies
(
)
self
.
render
(
'logout.html'
)
class
Application
(
tornado
.
web
.
Application
)
:
def
__init__
(
self
)
:
handlers
=
[
(
r
'/'
,
TwitterHandler
)
,
(
r
'/logout'
,
LogoutHandler
)
]
settings
=
{
'twitter_consumer_key'
:
'cWc3 ... d3yg'
,
'twitter_consumer_secret'
:
'nEoT ... cCXB4'
,
'cookie_secret'
:
'NTliOTY5NzJkYTVlMTU0OTAwMTdlNjgzMTA5M2U3OGQ5NDIxZmU3Mg=='
,
'template_path'
:
'templates'
,
}
tornado
.
web
.
Application
.
__init__
(
self
,
handlers
,
*
*
settings
)
if
__name__
==
'__main__'
:
app
=
Application
(
)
server
=
tornado
.
httpserver
.
HTTPServer
(
app
)
server
.
listen
(
8000
)
tornado
.
ioloop
.
IOLoop
.
instance
(
)
.
start
(
)
|
例子7-2和例子7-3的模板都应该放到应用程序目录的 templates 目录中.
例子7-2:home.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<
html
>
<
head
>
<
title
>
{
{
user
[
'name'
]
}
}
(
{
{
user
[
'screen_name'
]
}
}
)
on
Twitter
<
/
title
>
<
/
head
>
<
body
>
<
div
>
<
a
href
=
"/logout"
>
Sign
out
<
/
a
>
<
/
div
>
<
div
>
<
img
src
=
"{{ user['profile_image_url'] }}"
style
=
"float:left"
/
>
<
h2
>
About
@
{
{
user
[
'screen_name'
]
}
}
<
/
h2
>
<
p
style
=
"clear:both"
>
<
em
>
{
{
user
[
'description'
]
}
}
<
/
em
>
<
/
p
>
<
/
div
>
<
div
>
<
ul
>
<
li
>
{
{
user
[
'statuses_count'
]
}
}
tweets
.
<
/
li
>
<
li
>
{
{
user
[
'followers_count'
]
}
}
followers
.
<
/
li
>
<
li
>
Following
{
{
user
[
'friends_count'
]
}
}
users
.
<
/
li
>
<
/
ul
>
<
/
div
>
{
%
if
'status'
in
user
%
}
<
hr
/
>
<
div
>
<
p
>
<
strong
>
{
{
user
[
'screen_name'
]
}
}
<
/
strong
>
<
em
>
on
{
{
' '
.
join
(
user
[
'status'
]
[
'created_at'
]
.
split
(
)
[
:
2
]
)
}
}
at
{
{
user
[
'status'
]
[
'created_at'
]
.
split
(
)
[
3
]
}
}
<
/
em
>
<
/
p
>
<
p
>
{
{
user
[
'status'
]
[
'text'
]
}
}
<
/
p
>
<
/
div
>
{
%
end
%
}
<
/
body
>
<
/
html
>
|
例子7-3:logout.html
1
2
3
4
5
6
7
8
9
10
11
|
<
html
>
<
head
>
<
title
>
Tornadoes
on
Twitter
<
/
title
>
<
/
head
>
<
body
>
<
div
>
<
h2
>
You
have
successfully
signed
out
.
<
/
h2
>
<
a
href
=
"/"
>
Sign
in
<
/
a
>
<
/
div
>
<
/
body
>
<
/
html
>
|
我们现在这里停一下,逐步对应用进行分析.首先从 twitter.py 程序开始,在 application 类的 init 方法中,你应该注意到 settings 中有两个新的关键词 twitter_consumer_key 和 twitter_consumer_secret.这里需要你将 twitte 应用配置页面上列出的参数添加到上面.同时你应该还注意到有两个处理的类: twitterhandler 和 logouthandler. 让我们花几分钟看一看它们.
未完待续..