前言 現在有規模的平台提供商基本上都會提供 Single Sign On (SSO)的登入機制,藉由他們的平台讓一般小網站可以讓使用者免密碼登入,雖然有很多種 Oauth2 的套件,不過我還是喜歡自己打 🤣,下面就開始嚕!
實作 在views/
建立一個line_login_auth.html
,填入以下內容,主要是實作一個按鈕並透過 javascript 來互叫 api 並導向到 LINE 頁面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <html > <head > <meta charset ="UTF-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <meta http-equiv ="X-UA-Compatible" content ="ie=edge" /> <title > LINE Login</title > </head > <body > <button class ="auth" onclick ="auth()" > LINE LOGIN</button > </body > <script src ="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js" > </script > <script > function auth ( ) { $.ajax({ url: 'https://YOUR_SERVERLESS_DOMAIN.amazonaws.com/dev/line/auth' , method: 'POST' , success: function (data ) { window .location.replace(data.result); }, }); } </script > </html >
接著我們在.env
加入 LINE Login 的環境變數並輸入對應的參數
1 2 3 LINE_LOGIN_CLIENT_ID= LINE_LOGIN_SECRET= LINE_LOGIN_URI=https://YOUR_SERVERLESS_DOMAIN.amazonaws.com/dev/line/auth
最後就是我們最重要的部分了,實作 GET a& POST 的 api,這邊我先從 POST 講起,這邊主要是把環境變數組在網址中回傳給前端,這邊需要注意的是state
他是在回傳回來的 JWT 需要解碼時所使用的,這邊我簡單打個字串,然而實際上會隨機產個變數,確保解碼的安全。
GET 則是負責處理從 LINE 回來的參數們,這邊我只用回傳回來的state
把id_token
解開,拿到裡面的使用者的參數,後續可以參照應用把資訊存下來,放入 session 並告知前端已登入。
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 from flask import requestfrom flask_restful import Resourceimport requestsimport webbrowserimport osimport jwtimport jsonclass LineLoginController (Resource ): def get (self ): r = requests.post( "https://api.line.me/oauth2/v2.1/token" , data={ "grant_type" : "authorization_code" , "code" : request.args.get('CODE' ), "redirect_uri" : os.environ.get('LINE_LOGIN_URI' ), "client_id" : os.environ.get('LINE_LOGIN_CLIENT_ID' ), "client_secret" : os.environ.get('LINE_LOGIN_SECRET' ), }, headers={"Content-Type" : "application/x-www-form-urlencoded" }) payload = json.loads(r.text) print(payload) token = payload.get("id_token" ) if token is None : return {'result' : payload['error_description' ]}, 400 state = request.args.get('state' ) token = token.encode() dt = jwt.decode(token, state, None , algorithms=['HS256' ]) if dt: return {'result' : dt}, 200 def post (self ): r_uri = os.environ.get("LINE_LOGIN_URI" ) client = os.environ.get("LINE_LOGIN_CLIENT_ID" ) state = "nostate" uri = f"https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={client} &redirect_uri={r_uri} &scope=profile%20openid%20email&state={state} " return {'result' : uri}
JWT 解開的參數如下,sub
就是 LINE 的唯一值 ID,一般拿到這個就可以知道是來自 LINE 的使用者了。
1 2 3 4 5 6 7 8 9 10 11 12 13 { "result" : { "iss" : "https://access.line.me" , "sub" : "Ud6ab866a67xxxxxxxxb12b0baffb8ac" , "aud" : "1622939248" , "exp" : 1569925548 , "iat" : 1569921948 , "amr" : ["linesso" ], "name" : "NiJia Lin" , "picture" : "https://profile.line-scdn.net/0hKvTocMuLFFlpFj9HpKdrDlVTGjxxxxxxxxx1YHACMOPRtEHmkWJVIKUyUJNkQWGT0X" , "email" : "loxxxxx09@gmail.com" } }
最後別忘了要在api.py
加入路由才會被導出去ㄛ
1 2 from controller.line_login_controller import LineLoginControllerapi.add_resource(LineLoginController, '/line/auth' )
結論 在實作的時候不知道為什麼一直不能讓 python 去幫我把用戶導去 LINE 認證的頁面,我記得之前寫 Ruby 可以啊(?),最後只好讓前端去幫我把使用者導出去,這邊就帶大家做到這嚕!