在多個產品網站,擁有不同網域的情況下,想要讓登入統一集中,像是 Google 那樣 (Youtube, Google Drive,Gmail ...etc) 的產品登入方式,嘗試由以下方式實現。

架構如下圖,是本篇需要實作的架構: architecture

產品區塊

產品伺服器會自己檢查判斷需不需要重新登入,然後跳轉到登入頁面做登入。

登入頁面那一端,會讓登入後的使用者回傳到這裡來,然後會導向到這裡網站的一個路由 signin (自訂),並且帶有一個參數:

http://demo.com/signin?key=1sa09fk...as0d  

這一個參數得到後,你就能向獨立的資料庫查詢 key 的值,並且利用 value 中的有限資訊來進行登入,並且在完成後要把這組 key 的這筆登入資料刪除。

獨立資料庫可以避免其他獨立的產品直接訪問使用者的個人資訊,提升兩者的安全性,並強調功能不一樣。

登入區塊

登入成功後,資料庫要建立一組 key, 並且對應一個值,這個值要放入使用者的登入資訊。

然後把 key 將它作為 http get 的 parameter ,叫使用者導向到那裡。

這個步驟主要是在獨立的資料庫中建立使用者有沒有登入的資料,如果被任何產品拿來登入後,就把這一組資料刪除,避免被利用。

獨立資料庫放使用者登入的資訊未必是一定要做的,你也可以傳送加密的文字,但是切記這個密文最好都能不一樣,可以穩固被有心人士攔截後拆解。

SQL Function

這裡的事情都可以在 SQL 完成,提供參考做法:

Table - signin_temporary
CREATE UNLOGGED TABLE signin_temporary  
(
  uuid_key uuid NOT NULL,
  signin_cookie jsonb NOT NULL,
  CONSTRAINT signin_temporary_pkey PRIMARY KEY (uuid_key)
)

建立好登入記錄資料庫的表,其中 (uuid_key) 當做 key 來使用,signin_cookie 則是拿出來就可以直接當 cookie 設定了,這個步驟是在寫入之前就做好相關的邏輯設計,需要自行實現。

Function - signin_use_key
CREATE OR REPLACE FUNCTION signin_use_key(  
    IN _e_mail character varying,
    IN _password character varying,
    IN _product_name text)
  RETURNS TABLE(successful boolean, status_code integer, private_key uuid, user_data jsonb, redirect_url text) AS
$$
DECLARE  
        _id             BIGINT;
        _first_name     TEXT;
        _last_name      TEXT;
        _private_key    UUID;
        _user_cookie    JSONB;
        _redirect_url   TEXT;
BEGIN

    IF (
            NOT is_ascii ( _e_mail     ) OR
            NOT is_ascii ( _password   )
        ) THEN
            RETURN QUERY SELECT false,4,uuid_generate_v4(),jsonb_build_object(
            'status','Failed!'
        ),null; -- code 4 is NOT ASCII
        END IF;

    SELECT
            id,          
            data -> 'data' ->> 'first_name',
            data -> 'data' ->> 'last_name'
        INTO
            _id,      
            _first_name,
            _last_name
        FROM users
        WHERE
            data -> 'data' ->> 'e_mail'   = _e_mail
        AND
            data -> 'data' ->> 'password' = crypt(_password, data -> 'data' ->> 'password');

        -- ------------

        IF ( _id IS NOT NULL ) THEN --IF USER ID DOES'NT FAILED!
        _private_key := uuid_generate_v4();
        _user_cookie := jsonb_build_object(
               'status'    , 'SUCCESS',
               'id'        , _id,          
               'first_name', _first_name,
               'last_name' , _last_name
            );

        INSERT INTO signin_temporary VALUES(_private_key,_user_cookie); --PUT KEY IN signin_temporary

            SELECT info->'signin_redirect_url' INTO _redirect_url FROM production_info WHERE product_name = _product_name;
        IF (_redirect_url IS NULL) THEN
            SELECT null INTO _redirect_url;
        END IF;

        RETURN QUERY SELECT true,1,_private_key,_user_cookie,_redirect_url; -- RETURN signin data!
    ELSE
        RETURN QUERY SELECT false,3,uuid_generate_v4(),jsonb_build_object(
            'status','Failed!'
        ),null;
        -- code 3 is EMAIL OR PASSWORD WRONG!
        END IF;
END;  
$$ LANGUAGE plpgsql;

而這組 function 主要是在登入區塊,進行登入後可以獲得一把 key 還有登入狀態,並且可以拿來回傳產品。

不過需要注意的另一點是,這些產品的 signin 路由的網址,可能還會需要用新的 sql table 來記錄每個產品 signin 要回傳的網址,因為如果直接用輸入的回傳網址來做跳轉,沒有資料正確性,很容易發生資料安全的問題。

Function - signin_get_value_by_key
CREATE OR REPLACE FUNCTION signin_get_value_by_key(_key uuid)  
  RETURNS jsonb AS
$$
DECLARE  
    _signin_cookie_value_data JSONB;
BEGIN  
    SELECT signin_cookie INTO _signin_cookie_value_data FROM signin_temporary WHERE uuid_key = _key;
    DELETE FROM signin_temporary WHERE signin_cookie->>'id' = _signin_cookie_value_data->>'id';
    RETURN _signin_cookie_value_data;
END;  
$$ LANGUAGE plpgsql;

這個 function 是給產品用的,產品只要顧著用這個 function 來取得使用者登入應有的資訊就可以了。