Jun 10 2010

在App Engine上使用OpenID验证登陆 {1}

Google App Engine最新发布的SDK 1.3.4提供了对OpenID的原生支持。下面我们就来演示在app engine上的应用如何使用OpenID来登陆。

首先你需要登陆到App Engine的管理控制台中,在你应用的"Application Settings"中选择"Authentication Options"为 "(Experimental) Federated Login"并保存。

Authentication Options
一旦你设置好了上面这一步,App Engine的Users API就能够提供对OpenID的支持了。在使用Users API之前,我们先看看OpenID的登陆流程,我按照自己的理解画了一个示意图。

  1. 用户访问web app
  2. web app返回一个登陆表单,提供OpenID identifier的输入框
  3. 用户输入OpenID identifier,提交表单
  4. web app根据用户输入的OpenID identifier用create_login_url()函数生成并重定向到OpenID登陆页面
  5. 用户在OpenID provider处登陆,并授权web app获取用户的OpenID信息
  6. OpenID provider把用户重定向到web app(重定向的url包含了用户的OpenID信息)
  7. web app通过get_current_user()函数获取到用户信息,登陆完成

上面的流程省略掉了一些步骤和细节,比如web app和OpenID provider的交互,web app对重放攻击的处理等等。这些步骤和细节App Engine全都帮我们做了,所以对web开发者来说是透明的,我们不用去关心。我们需要做的就是第2,4,7步。

提供一个用户登陆页面
准备一个简单login.html文件内容如下,主要就是提供一个能够接收用户OpenID标识的表单。

<html>
<head>
  <title>Log in with OpenID</title>
</head>
<body>
  <h1>Log in with OpenID</h1>
  <form method="get" action="/testopenid">
    <input type="text" name="openid" />
    <input type="submit" value="Log In" />
  </form>
</body>
</html>



修改app.yaml,让用户访问 http://your-app.appspot.com/testopenid 时能显示上面的login.html

- url: /testopenid
  script: testopenid.py



添加testopenid.py文件,内容如下。

import os

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import users
from google.appengine.ext.webapp.util import run_wsgi_app

class UsersHandler(webapp.RequestHandler):
  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'templates', 'login.html')
    self.response.out.write(template.render(path,{}))

application = webapp.WSGIApplication(
             [('/', MainPage),
             ('/testopenid',UsersHandler)],
             debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()



生成OpenID登陆url并重定向
当用户提交OpenID identifier后,我们用Users API中的create_login_url()函数生成需要跳转的url。具体的函数定义见这里.
修改testopenid.py中的UsersHandler类

class UsersHandler(webapp.RequestHandler):
  def get(self):
    openid_url = self.request.GET.get('openid')
    if not openid_url:
      path = os.path.join(os.path.dirname(__file__), 'templates', 'login.html')
      self.response.out.write(template.render(path,{}))
    else:
      self.redirect(users.create_login_url('/testopenid', None, openid_url))



验证用户登陆,并取得OpenID信息
当用户在OpenID Provider处登陆并跳转到我们的web app时,我们用Users API中的get_current_user()函数验证用户是否登陆并返回一个User对象,这个对象中就保存着登陆用户的OpenID信息.
继续修改我们的UsersHandler类

class UsersHandler(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()
    openid_url = self.request.GET.get('openid')
    if user:
      greeting = ("Welcome, %s! %s(sign out)" %
                    (user.__dict__, user.nickname(),
                     users.create_logout_url("/testopenid")))
      self.response.out.write(greeting)
    else:
      if not openid_url:
        path = os.path.join(os.path.dirname(__file__), 'templates', 'login.html')
        self.response.out.write(template.render(path,{}))
      else:
        self.redirect(users.create_login_url('/testopenid', None, openid_url))



到目前为止我们的web app已经能够支持OpenID登陆了。可以看到我们所需要做的工作非常简单,App Engine给我们处理了大量的细节,生活顿时变的非常美好。

还有一点需要注意。如果你在app.yaml中针对某个url设置成必须要先登录(login: required),那么当你未登录访问那个url时,App Engine会把你重定向到 http://your-app.appspot.com/_ah/login_required 对于这种情况你需要做的就是针对"/_ah/login_required"这个地址创建一个登陆页面。

demo在这里

参考文章 Using OpenID authentication on App Engine


Apr 26 2010

没有距离的日子 {1}

物理学家维纳曾说,在理论上人可以通过一根电线来传输。现在理论过去几百年早已成了现实。在我这个时代,所有的物质(包括生命体和非生命体)发生位移都是通过“电线”来完成,这里的电线当然与古代的有所不同,因为我是为了让你容易理解所以还是姑且称之为电线。简单的说,只要A、B两地都连入电网,并且都有一个传输终端(国人俗称此设备为“打炮”),那么任何物质都可以方便快捷的在两地间传输。

我还是简单地描述下我的世界。现在的地球上已经没有了公路铁路机场之类的交通设施,甚至连交通一词也变得陌生。原来的通用汽车成了玩具厂商,波音公司在80年前被NASA收购后跑到月亮上做航空设备去勒。现在世界500强企业第一名永远都是中国电网,理由显而易见:一是因为垄断,二是原来所谓的交通全部并入了电力公司的范围。

就拿我来说,本人在纽约上班,家住毛里求斯岛的火山岛上。每天都要从那个岛上通过电网传送到公司。以我百来斤的体重加上从毛里求斯岛到纽约的距离,每一次传送的费用大概是50美分(当然这是按照你们那个时代的物价水平估算的)。还好本人天生苗条,像我一个大胖子同事家到公司的距离没我远,但每个月的传输费却是我的三倍。正因为现在出门费用与体重挂钩,所以现在人们的身材普遍不错。这有点扯远了,关于打炮一事我还可以说得具体点:每天8点50我走出门,坐在门外的一个类似马桶的设备上,这个马桶就是传输终端。坐好后,我再把自己的目的地编号输入马桶前面两腿中间的仪器上()。然后再在右手边的指纹识别器上按上自己的食指(按哪个指头随便你),要求对方的终端验证我的身份,要是不经过验证就能随便传送那这个世界就乱套啦。验证通过后,只要按一下start按钮我就变成了一堆信息,通过电线开始传输,然后在对方的终端组装起来。当然这些我是感觉不到的,唯一的感觉就是一阵酥麻,等我再次有意识的时候,我已经到了纽约的公司门口。整个过程大概2分钟。

不知道你注意到没有,我家的终端和公司的终端都是放在门外的。为什么不直接放在屋内呢,还省去了开门。这样做的原因主要是为了安全。曾经有黑客破译了终端的验证系统,结果就跑到别人家随便打炮去了。说到这个验证系统我还可以补充一下,前面我是通过指纹来验证,其实有很多种验证方式。如果你是残疾人没有手,你可以通过虹膜来验证----就是拿眼睛往一个小洞瞅瞅就行了。如果你既没手又没眼睛,你还可以通过血液的DNA来验证。如果你既没双手又没眼睛并且全身又没有一滴血,那你将会按照非生命体的标准来传输。比如我要把一瓶酒送到家住上海的女朋友家。我就要先把酒放在“马桶”上,输入girlfriend家的终端编号后再按一下分析按钮。这瓶酒就被打散成一堆信息,系统根据这堆信息算出一个标识这瓶酒的唯一编码然后发送给对方的终端。老婆在她终端上一看是瓶酒,就会按下allow按钮。最后这瓶酒就会在几秒钟内传送过去。

关于非生命体在终端上进行编码我再多说两句。这种编码类似在你们的计算机上用SHA算法计算一个文件的SHA值,一个SHA值可以唯一标识一个文件。同样的,对物质进行编码也有一种算法(好像学名叫lamengao算法)。通过这种算法计算出来的值也可以唯一标识一个物质。只不过这种算法比MD5算法复杂多了。当一个文件只要改动一比特,那么这个文件的SHA值就变了。但当物质有所改变时,lamengao算法计算出来的值不一定会改。比如我送给女朋友的那瓶酒,当我摸一下那瓶酒那么这瓶酒所带的信息肯定有所变化。如果用SHA算法的话,就会发生这种情况:当我老婆拿到这瓶酒时,这瓶酒就不再是我给她的那瓶了。如果这样,那所有的直销商店都可以关门了。

也许你会说,在你们这个时代不再有交通事故了吧。其实不然。如果我们的“交通”发生事故将会比古代更可怕。著名的911大灾难就是这样的一起事故。6年前的9月11号,某恐怖组织在下班的高峰期切断了纽约的部分电网,结果令5000多正在电网中传输的人瞬间灰飞烟灭。什么叫尸骨无存,这才叫真正的尸骨无存。还好我那天晚下班5分钟,不然我也将化为信息永远飘散在宇宙中。

我常常在想一个问题,打炮之前的我还是组装过后的我吗?
。。。。。。。。。。。。。。。待续