Project <<
Previous Next >> API
Waitress
採 waitress 執行 app.py
start_app.py 程式碼:
from waitress import serve
from app import app # 這裡導入 app.py 中的 Flask 應用
# 這裡的 127.0.0.1:5000 是服務監聽的地址與端口
serve(app, listen='127.0.0.1:5000', threads=8)
利用 waitress 執行的 app.py 程式碼:
# pip install Flask google-api-python-client google-auth
from flask import Flask, render_template, request, redirect, url_for, flash
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
app = Flask(__name__)
app.secret_key = os.urandom(24)
# 組態設定
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "yout_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
PASSWORD_SECRET = "your_passwod_secret"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']
def create_user(email_prefix, password):
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
service = build('admin', 'directory_v1', credentials=delegated_credentials)
user_body = {
"primaryEmail": f"{email_prefix}@{DOMAIN}",
"name": {
"givenName": email_prefix,
"familyName": "User"
},
"password": password
}
user = service.users().insert(body=user_body).execute()
return user
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
secret = request.form['secret']
prefix = request.form['prefix']
account_password = request.form['account_password']
confirm_password = request.form['confirm_password']
# 權限驗證
if secret != PASSWORD_SECRET:
flash("通關密碼錯誤!")
return redirect(url_for('index'))
if not prefix.isdigit():
flash("請輸入您的學號")
return redirect(url_for('index'))
# 密碼確認
if account_password != confirm_password:
flash("兩次密碼不一致,請重新確認!")
return redirect(url_for('index'))
# 密碼驗證(可加入更多規則)
if len(account_password) < 8:
flash("密碼長度必須至少 8 個字元!")
return redirect(url_for('index'))
try:
result = create_user(prefix, account_password)
flash(f"帳號建立成功: {result['primaryEmail']}")
except Exception as e:
flash(f"建立帳號失敗: {str(e)}")
return redirect(url_for('index'))
return render_template('form.html')
# 不使用 app.run(),讓 waitress 來處理
配合使用的 nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen [::]:80 ipv6only=on;
server_name your_first_server_domain your_server_cname_domain;
#root html;
# 將 port 80 http 連結到倉儲的靜態網站
root C:/your_static_site_directory/wcm2025_hw;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen [::]:443 ssl ipv6only=on;
http2 on;
server_name your_first_server_domain your_server_cname_domain;
ssl_certificate fullchain.pem;
ssl_certificate_key privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
# https port 443 的連結資料是由近端 IPv6 or IPv4 port 8080 所提供
#proxy_pass http://[::1]:5000;
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
初步測試所使用的單一通關密碼進行驗證,可以再透過 flask-pyoidc 改為 OIDC 認證通關:
先安裝必要模組:
pip install Flask flask-pyoidc google-api-python-client google-auth
接著登入 Google Cloud Console 建立 OAuth 2.0 憑證:
- 登入 Google Cloud Console
- 選擇你的專案,點選「建立憑證」>「OAuth 用戶端 ID」
- 選擇「Web 應用程式」
- 設定授權的重新導向 URI(Redirect URI)
- 例如:http://localhost:5000/oidc/callback
- 取得「用戶端 ID」與「用戶端密鑰」
接著建立 OIDC 設定檔:
將下列內容儲存為 oidc_secrets.json(請填入你自己的 client_id/client_secret):
{
"web": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"issuer": "https://accounts.google.com",
"redirect_uris": ["http://localhost:5000/oidc/callback"]
}
}
利用 OIDC 認證通關的 app.py:
# pip install Flask flask-pyoidc google-api-python-client google-auth
from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_pyoidc import OIDCAuthentication
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
import json
app = Flask(__name__)
app.secret_key = os.urandom(24)
# === OIDC 設定 ===
with open('oidc_secrets.json') as f:
oidc_conf = json.load(f)['web']
issuer = oidc_conf['issuer']
client_metadata = ClientMetadata(
client_id=oidc_conf['client_id'],
client_secret=oidc_conf['client_secret'],
post_logout_redirect_uris=[oidc_conf['redirect_uris'][0]]
)
provider_config = ProviderConfiguration(
issuer=issuer,
client_metadata=client_metadata
)
auth = OIDCAuthentication({'default': provider_config}, app)
# === Google Workspace 設定 ===
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "your_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']
def create_user(email_prefix, password):
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
service = build('admin', 'directory_v1', credentials=delegated_credentials)
user_body = {
"primaryEmail": f"{email_prefix}@{DOMAIN}",
"name": {
"givenName": email_prefix,
"familyName": "User"
},
"password": password
}
user = service.users().insert(body=user_body).execute()
return user
@app.route('/logout')
def logout():
# 清除 session 並 redirect 回首頁
session.clear()
return redirect(auth.client_end_session_uri('default', url_for('index', _external=True)))
@app.route('/', methods=['GET', 'POST'])
@auth.oidc_auth('default')
def index():
# 取得登入者資訊
user_session = auth.user_session
user_email = user_session.userinfo['email']
# 只允許特定網域
if not user_email.endswith('@your_google_workspace_domain'):
return "無權限,請使用組織帳號登入", 403
if request.method == 'POST':
prefix = request.form['prefix']
account_password = request.form['account_password']
confirm_password = request.form['confirm_password']
if not prefix.isdigit():
flash("請輸入您的學號")
return redirect(url_for('index'))
if account_password != confirm_password:
flash("兩次密碼不一致,請重新確認!")
return redirect(url_for('index'))
if len(account_password) < 8:
flash("密碼長度必須至少 8 個字元!")
return redirect(url_for('index'))
try:
result = create_user(prefix, account_password)
flash(f"帳號建立成功: {result['primaryEmail']}")
except Exception as e:
flash(f"建立帳號失敗: {str(e)}")
return redirect(url_for('index'))
return render_template('form.html', user_email=user_email)
配合 OIDC 認證的 form.html:
<!doctype html>
<title>建立 Google Workspace 帳號</title>
<h2>建立 Google Workspace 帳號</h2>
<p>目前登入:{{ user_email }}</p>
<form method="POST">
<label>學號(Email @ 前):<input type="text" name="prefix" required pattern="\d+"></label><br>
<label>新帳號密碼:<input type="password" name="account_password" required></label><br>
<label>再次輸入密碼:<input type="password" name="confirm_password" required></label><br>
<input type="submit" value="送出">
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">登出</a>
之後假設透過收集用戶的 email address 存為 allowed_email.txt, 採 OIDC 認證流程時,只允許電子郵箱在此檔案中的用戶有權限利用 app.py 建立 Google Workspace 帳號, 則 app.py 配合修改如下:
from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_pyoidc import OIDCAuthentication
from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
import json
app = Flask(__name__)
app.secret_key = os.urandom(24)
# 讀取 allowed_email.txt,存成 set
def load_allowed_emails(filepath="allowed_email.txt"):
with open(filepath, encoding="utf-8") as f:
return set(line.strip().lower() for line in f if line.strip())
ALLOWED_EMAILS = load_allowed_emails()
# === OIDC 設定 ===
with open('oidc_secrets.json') as f:
oidc_conf = json.load(f)['web']
issuer = oidc_conf['issuer']
client_metadata = ClientMetadata(
client_id=oidc_conf['client_id'],
client_secret=oidc_conf['client_secret'],
post_logout_redirect_uris=[oidc_conf['redirect_uris'][0]]
)
provider_config = ProviderConfiguration(
issuer=issuer,
client_metadata=client_metadata
)
auth = OIDCAuthentication({'default': provider_config}, app)
# === Google Workspace 設定 ===
DOMAIN = "your_google_workspace_domain"
ADMIN_EMAIL = "your_admin_email"
SERVICE_ACCOUNT_FILE = "C:/your_service_account_file_directory/service.json"
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user']
def create_user(email_prefix, password):
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(ADMIN_EMAIL)
service = build('admin', 'directory_v1', credentials=delegated_credentials)
user_body = {
"primaryEmail": f"{email_prefix}@{DOMAIN}",
"name": {
"givenName": email_prefix,
"familyName": "User"
},
"password": password
}
user = service.users().insert(body=user_body).execute()
return user
@app.route('/logout')
def logout():
session.clear()
return redirect(auth.client_end_session_uri('default', url_for('index', _external=True)))
@app.route('/', methods=['GET', 'POST'])
@auth.oidc_auth('default')
def index():
user_session = auth.user_session
user_email = user_session.userinfo['email'].lower()
# 只允許在 allowed_email.txt 的 email
if user_email not in ALLOWED_EMAILS:
return render_template('not_allowed.html', user_email=user_email), 403
if request.method == 'POST':
prefix = request.form['prefix']
account_password = request.form['account_password']
confirm_password = request.form['confirm_password']
if not prefix.isdigit():
flash("請輸入您的學號")
return redirect(url_for('index'))
if account_password != confirm_password:
flash("兩次密碼不一致,請重新確認!")
return redirect(url_for('index'))
if len(account_password) < 8:
flash("密碼長度必須至少 8 個字元!")
return redirect(url_for('index'))
try:
result = create_user(prefix, account_password)
flash(f"帳號建立成功: {result['primaryEmail']}")
except Exception as e:
flash(f"建立帳號失敗: {str(e)}")
return redirect(url_for('index'))
return render_template('form.html', user_email=user_email)
templates/form.html
<!doctype html>
<title>建立 Google Workspace 帳號</title>
<h2>建立 Google Workspace 帳號</h2>
<p>目前登入:{{ user_email }}</p>
<form method="POST">
<label>學號(Email @ 前):<input type="text" name="prefix" required pattern="\d+"></label><br>
<label>新帳號密碼:<input type="password" name="account_password" required></label><br>
<label>再次輸入密碼:<input type="password" name="confirm_password" required></label><br>
<input type="submit" value="送出">
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<a href="{{ url_for('logout') }}">登出</a>
另外新增一個 templates/not_allowed.html
<!doctype html>
<title>未授權訪問</title>
<h2>未授權訪問</h2>
<p>您的 email({{ user_email }})不在允許名單中,無法建立帳號。</p>
<a href="{{ url_for('logout') }}">登出</a>
Project <<
Previous Next >> API