感謝 chriis 的工作,文章寫得很好。
但是我在實踐的過程中,遇到了一些問題,也輾轉最終解決了。我將記錄下實踐過程,以我的風格。
要求:本地有一個可以跑得通的 Phoenix 應用(不要求必須得用LiveView),並且已經有了可用的身份驗證系統(用户系統)
一些創建和配置工作
到 Google 開發者後台,創建一個新的項目,或者選擇已有項目。
- 點擊進入到剛才創建的項目主頁
- 點擊左側菜單欄中的「OAuth 權限請求頁面」
- 點擊左側菜單欄中的「菜單」-> 「開始」,進入到創建應用頁面,完成基本信息的填寫。
- 點擊「創建 OAuth 客户端」,填寫基本信息。這裏有兩個重要字段需要填充:
- 「已獲授權的 JavaScript 來源」填你項目的測試、生產環境的網址,比如:http://localhost:4000、https://xxxxx.com
- 「已獲授權的重定向 URI」填你項目的測試、生產環境的網址加上「/auth/google/callback」(實際上這個路徑取決於後面你項目代碼中聲明的路徑),比如:http://localhost:4000/auth/google/callback、https://xxxxx.com/auth/google/callback
- 同時單獨保存下該頁面上出現的「客户端 ID」和「客户端密鑰」(另外找安全的地方保存。一段時間後頁面上將不支持保存密鑰)
- 點擊底部的創建或保存按鈕。
複製粘貼一些模板代碼
接下來回到 Phoenix 項目中。
- 安裝unberauth
# 在 mix.exs 文件中追加
{:ueberauth_google, "~> 0.10.8"}
運行:mix deps.get 下載依賴
- 配置
首先是:config/dev.exs
config :ueberauth, Ueberauth,
providers: [
google: {Ueberauth.Strategy.Google, [default_scope: "email profile"]}
]
然後是:config/prod.exs
config :ueberauth, Ueberauth,
providers: [
google: {Ueberauth.Strategy.Google, [default_scope: "email profile", callback_scheme: "https"]}
]
上面這兩個的區別是 prod 環境追加了:callback_scheme,unberauth的默認值是 http,正好對應我開發環境是:http://localhost:4000,也對應前面在Google Dashboard的配置。這一點在 chriis 的文章中沒有提及,但是必要的,否則會導致 Google 登錄失敗。
最後是 config/runtime.exs
# 還記得前面讓你保存「客户端 ID」和「客户端密鑰」嗎?
# 到系統環境變量中設置,然後在這裏讀取到程序中
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
- 調整用户數據表
記得將下面內容中的 XXXXX 替換為你的應用名
在 lib/dokuya/accounts/user.ex 文件中追加:
schema "users" do
...
field :is_oauth_user, :boolean, default: false
...
end
# 你可以根據你的實際user情況,調整這裏的字段。
def oauth_registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email])
|> validate_required([:email])
|> validate_email(opts)
|> put_change(:is_oauth_user, true)
end
新增加了 schema 字段後,記得也創建一個新的數據庫遷移文件:mix ecto.gen.migration add_is_oauth_user_to_users
在新生成的 migration 文件中追加:
defmodule Dokuya.Repo.Migrations.AddOauthUser do
use Ecto.Migration
def change do
alter table(:users) do
add :is_oauth_user, :boolean, default: false
# 因為 oauth 登錄不需要密碼,所以將 hashed_password 字段設置為可空
modify :hashed_password, :string, null: true
end
end
end
然後運行遷移命令:mix ecto.migrate,修改數據庫結構。
在 lib/xxxxx/accounts.ex 文件中追加:
def register_oauth_user(attrs) do
%User{}
|> User.oauth_registration_changeset(attrs)
|> Repo.insert()
end
- 追加核心 Controller 代碼
在該目錄創建一個文件:lib/xxxxxx_web/controllers/google_auth_controller.ex
文件內容如下:
defmodule XXXXWeb.GoogleAuthController do
require Logger
use XXXXXWeb, :controller
plug Ueberauth
alias XXXXX.Accounts
alias XXXXXWeb.UserAuth
def request(conn, _params) do
Phoenix.Controller.redirect(conn, to: Ueberauth.Strategy.Helpers.callback_url(conn))
end
def callback(conn, params) do
create(conn, params, "Welcome back!")
end
# google login
defp create(%{assigns: %{ueberauth_auth: auth}} = conn, _params, info) do
email = auth.info.email
case Accounts.get_user_by_email(email) do
nil ->
# User does not exist, so create a new user
# 因為我只需要 email,所以這裏只傳遞 email,其實 auth.info 中有更多用户相關的信息可以使用。
case Accounts.register_oauth_user(%{
email: email
}) do
{:ok, user} ->
Logger.info("Google login success: #{inspect(user)}")
conn
|> put_flash(:info, info)
|> UserAuth.log_in_user(user, %{"remember_me" => "true"})
{:error, changeset} ->
Logger.error("Failed to create user #{inspect(changeset)}.")
conn
|> put_flash(:error, "Failed to create user.")
|> redirect(to: ~p"/")
end
user ->
# User exists, update session or other details if necessary
conn
|> put_flash(:info, info)
|> UserAuth.log_in_user(user, %{"remember_me" => "true"})
end
end
end
- 增加新的路由
到 lib/xxxx_web/router.ex 中追加
scope "/auth", DokuyaWeb do
# 記得追加這一步,否則登錄過程中會 fetch_session 相關的錯誤。
pipe_through :browser
get "/:provider", GoogleAuthController, :request
get "/:provider/callback", GoogleAuthController, :callback
end
- 最後一步,將登錄按鈕放到你的任何像放置的頁面上:
<.button href={~p"/auth/google"}>Login with Google</.button>
大功告成。現在用户可以通過 Google 登錄方便地訪問你的網站。