在前兩篇測試紀錄中, 我使用 Google 的 Datastore 資料儲存庫來儲存使用者資料, 其實 GAE 也提供 users API 模組可以讓我們利用 Gmail 帳戶進行基本的身分認證, 也就是使用者必須登入 Gmail 帳號後才能進入我們的網頁應用程式. 應用程式使用 users API 只能取得使用者的 email 帳號與暱稱, 因為認證是透過 Google 進行, 並非應用程式, 因此使用者不用擔心洩漏密碼等資訊.
接下來打算在之前的兩篇文章基礎上, 測試 Google 使用者登入模組的功能, 參考之前的文章 :
# GAE 的資料儲存 (Datastore)
# 如何在 GAE 中處理 HTTP 要求
# 如何在 GAE 上佈署 jQuery EasyUI 專案 (一)
# 如何在 GAE 上佈署 jQuery EasyUI 專案 (二)
# 如何在 GAE 上佈署 jQuery EasyUI 專案 (三)
下列測試延續上篇 "如何在 GAE 上佈署 jQuery EasyUI 專案 (三)" 中的測試三修改. 要使用 Google 帳號驗證首先必須匯入 users 模組, 在主控程式 main.py 中加入下列匯入指令 :
from google.appengine.api import users
呼叫此模組的 get_current_user() 函式會傳回一個 User 類別的實體, 其中包含了目前此瀏覽器已登入 Google 的使用者帳號資料 (暱稱與 email), 如果尚未登入則傳回 Null.
然後增加 google_user_login_1 這個 URL 路徑 (僅列出增加或修改部分) :
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3),
('/easyui_4', easyui_4),
('/login_4', login_4),
('/easyui_5', easyui_5),
('/login_5', login_5),
('/easyui_5_1', easyui_5_1),
('/home', home),
('/google_user_login_1', google_user_login_1)
], debug=True)
亦即當收到 /google_user_login_1 的存取要求時, 由 google_user_login_1 類別處理, 所以相對地也要增加處理此路徑的類別與 get 函式, 作法有三種, 第一種是以程式來判斷處理 :
class google_user_login_1(webapp2.RequestHandler):
def get(self):
user=users.get_current_user();
if user:
logout_url=users.create_logout_url(self.request.path)
content=("歡迎 %s!<br>您已經以 %s 帳號登入了! (<a href='%s'>登出</a>)" %
(user.nickname(), user.email(), logout_url))
else:
login_url=users.create_login_url(self.request.path)
content=("您尚未登入 (<a href='%s'>登入</a>)" % login_url)
self.response.out.write(content)
這裡我們用 users.get_current_user() 來取得此瀏覽器程序已登入 Google 帳號之使用者物件實體 user, 如果成功就顯示歡迎訊息, 並且附上帶有登出 Google 帳號之超連結. 透過呼叫 users 模組之 create_logout_url() 函式可取得登出 Google 帳號之 URL, 傳入參數是登出後要重導向之目的地 URL, 這裡我們傳入 self.request.path 表示登出後回到目前這個網址, 即 /google_user_login_1.
若使用者尚未登入 Google 帳號, 就顯示附有登入超連結的訊息, 利用呼叫 users 模組的 create_user_login() 函式即可取得 Google 登入畫面的 URL. 注意這裡的 content 是兩個 tuple 的資料結構, 使用與 C 語言的 printf() 格式化輸出函式類似的語法來將變數填入 tuple 內的 %s 格式變數, 兩個 tuple 以 % 隔開, 而且變數一一相對應. 這裡我們直接將 content 這個 tuple 透過 response.out 物件輸出, 不另外製作一個 html 模板檔案, 實際範例如下所示 :
在本地端測試結果如下 :
# Google Cloud : Utility Functions
如下面測試 2 所示, 首先我們添加一個 /google_user_login_2 路徑 :
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3),
('/easyui_4', easyui_4),
('/login_4', login_4),
('/easyui_5', easyui_5),
('/login_5', login_5),
('/easyui_5_1', easyui_5_1),
('/home', home),
('/google_user_login_1', google_user_login_1),
('/google_user_login_2', google_user_login_2)
], debug=True)
然後定義要求處理類別如下 :
class google_user_login_2(webapp2.RequestHandler):
@login_required
def get(self):
user=users.get_current_user();
logout_url=users.create_logout_url(self.request.path)
content=("歡迎 %s!<br>您已經以 %s 帳號登入了! (<a href='%s'>登出</a>)" %
(user.nickname(), user.email(), logout_url))
self.response.out.write(content)
由於必須通過 Google 登入才會進入 get() 函式, 因此這裡只要處理已登入情況即可. 實際測試連結如下 :
測試 2 : http://jqueryeasyui.appspot.com/google_user_login_2
效果是一樣的, 只是少了登入前的頁面而已.
第三種作法是在 app.yaml 檔案中針對必須登入後才能進入之 url 加入 login: required 來強制登入. 首先開啟 app.yaml 檔, 然後於 main.app 前面加入針對路徑 /google_user_login_3 的登入要求 :
application: jqueryeasyui
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon\.ico
- url: /static
static_dir: static
- url: /google_user_login_3
script: main.app
login: required
- url: .*
script: main.app
libraries:
- name: webapp2
version: "2.5.2"
注意, 此 URL 必須在 .* 之前, 否則不會被處理. 接著在 main.py 中加入 google_user_login_3 的路徑如下 :
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3),
('/easyui_4', easyui_4),
('/login_4', login_4),
('/easyui_5', easyui_5),
('/login_5', login_5),
('/easyui_5_1', easyui_5_1),
('/home', home),
('/google_user_login_1', google_user_login_1),
('/google_user_login_2', google_user_login_2),
('/google_user_login_3', google_user_login_3)
], debug=True)
然後加入其路徑處理類別, 我們可以沿用上面 google_user_login_2 的複製過來改成 google_user_login_3 即可 :
class google_user_login_3(webapp2.RequestHandler):
def get(self):
user=users.get_current_user();
logout_url=users.create_logout_url(self.request.path)
nickname=user.nickname()
content=("歡迎 %s!<br>您已經以 %s 帳號登入了! (<a href='%s'>登出</a>)" %
(user.nickname(), user.email(), logout_url))
self.response.out.write(content)
測試 3 : http://jqueryeasyui.appspot.com/google_user_login_3
可見效果與上面測試 2 是一樣的. 以上都是在 main.py 裡直接輸出網頁, 比較不好控制板型, 應該用模板檔案為宜. 在下面的測試 4 裡就改用模板來輸出網頁, 我在 templates 下增加了一個模板檔案 google_user_login_4.htm :
{% extends "jqueryeasyui.htm" %}
{% block style %}
body {font: 80% "Trebuchet MS", sans-serif; margin: 50px;}
{% endblock%}
{% block body %}
<p>歡迎! {{nickname}}<br>
您已經以 {{email}} 帳號登入了! (<a href="{{logout_url}}">登出</a>)</p>
{% endblock%}
這裡我們希望 main.py 在渲染此模板時能傳入 nickname, email, 以及 logout_url 這三個變數, 因此 main.py 增加了 google_user_login_4 的路徑與其要求處理類別如下 :
class google_user_login_4(webapp2.RequestHandler):
@login_required
def get(self):
url="templates/google_user_login_4.htm"
user=users.get_current_user();
nickname=user.nickname()
email=user.email()
logout_url=users.create_logout_url("/google_user_login_4")
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,
{'nickname':nickname,'email':email,'logout_url':logout_url})
self.response.out.write(content)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3),
('/easyui_4', easyui_4),
('/login_4', login_4),
('/easyui_5', easyui_5),
('/login_5', login_5),
('/easyui_5_1', easyui_5_1),
('/home', home),
('/google_user_login_1', google_user_login_1),
('/google_user_login_2', google_user_login_2),
('/google_user_login_3', google_user_login_3),
('/google_user_login_4', google_user_login_4)
], debug=True)
這裡為了簡單計 (懶得再去 app.yaml 中添加路徑處理項目), 採用測試 2 的 @login_required 裝飾字來強制登入, 同時在呼叫 template 類別的 render() 方法渲染模板時, 傳入了nickname, email, 以及 logout_url 三個變數. 注意, JSON 的索引全部都要用單引號括起來 (雙引號不行, 這是 JSON 的標準格式), 否則啥也傳不過去. 實際測試範例如下 :
測試 4 : http://jqueryeasyui.appspot.com/google_user_login_4
注意, 在渲染時不要傳遞含有 html 的字串, 否則會被當作文字處理, 例如剛開始時我把測試 3 的 content 當作變數傳過去, 這樣 google_user_logine_4.htm 模板裡只要用 {{content}} 接收即可, 但這樣卻得到如下渲染結果 :
歡迎 test@example.com!<br>您已經以 test@example.com 帳號登入了! (<a href='/_ah/login?continue=http%3A//localhost%3A11080/google_user_login_3&action=logout'>登出</a>)
另外, users 類別還有一個方法 is_current_user_admin() 可以判別登入者是否為 GAE 應用程式的管理者, 我在 main.py 中增加 google_user_login_5 路徑與其處理類別, 如下列測試 5 所示 :
class google_user_login_5(webapp2.RequestHandler):
@login_required
def get(self):
user=users.get_current_user();
logout_url=users.create_logout_url(self.request.path)
if users.is_current_user_admin():
content="您是此應用程式的管理者!"
else:
content=("歡迎 %s!<br>您已經以 %s 帳號登入了! (<a href='%s'>登出</a>)" %
(user.nickname(), user.email(), logout_url))
self.response.out.write(content)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3),
('/easyui_4', easyui_4),
('/login_4', login_4),
('/easyui_5', easyui_5),
('/login_5', login_5),
('/easyui_5_1', easyui_5_1),
('/home', home),
('/google_user_login_1', google_user_login_1),
('/google_user_login_2', google_user_login_2),
('/google_user_login_3', google_user_login_3),
('/google_user_login_4', google_user_login_4),
('/google_user_login_5', google_user_login_5)
], debug=True)
測試 5 : http://jqueryeasyui.appspot.com/google_user_login_5
如果是這個應用程式的管理者, 登入後會顯示 "您是此應用程式的管理者!", 否則顯示一般登入訊息, 我以兩個 Gmail 帳號測試, 其中一個是本應用程式 jqueryeasyui 的管理帳號, 一個不是, 確實輸出正確結果 :
OK, Google User API 的測試完成! 可見 Google 登入其實沒啥料, 只能取得帳號而已, 如果需要儲存更多的使用者資料, 例如性別, 職業, 電話, 喜好等會員資訊, 還是得自己在 Datastore 實作會員資料表.
以上測試參考了上官林傑的 "Google應用服務引擎開發實戰" 這本書中的說明, 此書已絕版 (圖書館有), 但範例程式還可以在下列網址下載 (只到第四章而已) :
# Google應用服務引擎開發實戰範例程式碼
沒有留言 :
張貼留言