init
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
import base64
|
||||
from flask import (
|
||||
Blueprint,
|
||||
make_response,
|
||||
redirect,
|
||||
request,
|
||||
jsonify,
|
||||
session,
|
||||
current_app,
|
||||
)
|
||||
import jwt
|
||||
import requests
|
||||
|
||||
|
||||
oauth_bp = Blueprint("oauth", __name__)
|
||||
|
||||
|
||||
# 客户端 ID
|
||||
# CLIENT_ID = "A17336FC-A8D7-4CEA-8777-384BFEABABC1"
|
||||
# BASE64_CLIENT = "Basic QTE3MzM2RkMtQThENy00Q0VBLTg3NzctMzg0QkZFQUJBQkMxOlU3LUMxQlpRZEw4VjVjLnp+cUFfNjRTTzJsOUYzSjBv"
|
||||
# # SSO 授权服务器地址
|
||||
# # "https://ssoalpha.dvb.corpinter.net/"
|
||||
# HOST = "https://ssoalpha.dvb.corpinter.net.cn/"
|
||||
# # 授权成功后,SSO 服务器重定向回你的应用的地址 (必须与 SSO 服务器上配置的一致)
|
||||
# REDIRECT_URL = "http://localhost:5221/report"
|
||||
# # 用户登录成功后,最终重定向到的前端页面地址
|
||||
# DOMAIN = "http://localhost:3001"
|
||||
|
||||
@oauth_bp.route("/error")
|
||||
def error():
|
||||
"""错误页面,用于显示认证失败信息"""
|
||||
msg = request.args.get("msg", "Unknown error")
|
||||
return f"Error: {msg}"
|
||||
|
||||
|
||||
# @oauth_bp.route("/report")
|
||||
@oauth_bp.route("/incident/authorized")
|
||||
def authorized():
|
||||
"""
|
||||
SSO 回调接口。
|
||||
接收 SSO 服务器返回的授权码 (code),并使用它来兑换访问令牌 (access_token)。
|
||||
"""
|
||||
CLIENT_ID = current_app.config["CLIENT_ID"]
|
||||
CLIENT_PASSWORD = current_app.config["CLIENT_PASSWORD"]
|
||||
HOST = current_app.config["SS0_HOST"] # 建议检查是否是笔误(SSO_HOST)
|
||||
REDIRECT_URL = current_app.config["REDIRECT_URL"]
|
||||
DOMAIN = current_app.config["DOMAIN"]
|
||||
|
||||
code = request.args.get("code")
|
||||
state = request.args.get("state", "/dashboard") # 默认值设为/dashboard
|
||||
|
||||
if not code:
|
||||
# return redirect(f"/error?msg=No authorization code provided.")
|
||||
return redirect(f"/incident/error?msg=No authorization code provided.")
|
||||
|
||||
# 1. 准备请求体,用授权码兑换 Token
|
||||
body = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": REDIRECT_URL,
|
||||
}
|
||||
|
||||
auth_str = f"{CLIENT_ID}:{CLIENT_PASSWORD}"
|
||||
# 2. 准备请求头
|
||||
headers = {
|
||||
"Authorization": "Basic "+base64.b64encode(auth_str.encode("utf-8")).decode("utf-8"),
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
|
||||
try:
|
||||
# 3. 发送 POST 请求到 SSO 服务器的 Token 端点
|
||||
response = requests.post(f"{HOST}v1/token", data=body, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
response_data = response.json()
|
||||
access_token = response_data.get("access_token")
|
||||
|
||||
# print('access_token', access_token)
|
||||
if not access_token:
|
||||
# return redirect(f"/error?msg=Failed to obtain access token. Response: {response_data}")
|
||||
return redirect(f"/incident/error?msg=Failed to obtain access token. Response: {response_data}")
|
||||
|
||||
# 4. 解析 JWT Token
|
||||
decoded_jwt = jwt.decode(access_token, options={"verify_signature": False})
|
||||
|
||||
# 5. 验证和清理 state 参数 (防止开放重定向攻击)
|
||||
safe_state = "/callback"
|
||||
print(f"Redirecting to: {DOMAIN}{safe_state}")
|
||||
|
||||
# 6. 设置 Cookie
|
||||
resp = make_response(redirect(f"{DOMAIN}{safe_state}"))
|
||||
|
||||
# 关键:设置 Cookie 域名和属性
|
||||
# domain = ".localhost" # 让所有 localhost 子域都能读取
|
||||
domain = None # 上线时改为None
|
||||
|
||||
# 设置 access_token
|
||||
resp.set_cookie(
|
||||
"authorization",
|
||||
access_token,
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False, # 前端需要读取
|
||||
samesite="Lax", # 允许跨站
|
||||
secure=False,
|
||||
) # 开发环境不需要HTTPS
|
||||
|
||||
# 设置登录名
|
||||
login_name = decoded_jwt.get("sub", "")
|
||||
resp.set_cookie(
|
||||
"loginName",
|
||||
login_name,
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False,
|
||||
samesite="Lax",
|
||||
secure=False,
|
||||
)
|
||||
|
||||
# 设置用户名 (直接存储,不需要Base64编码)
|
||||
user_name = decoded_jwt.get("name", "")
|
||||
print("user_name", user_name)
|
||||
resp.set_cookie(
|
||||
"userName",
|
||||
user_name, # 直接存储,前端处理编码
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False,
|
||||
samesite="Lax",
|
||||
secure=False,
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error exchanging code for token: {e}")
|
||||
return redirect(f"/error?msg=Error communicating with SSO server.")
|
||||
except jwt.InvalidTokenError as e:
|
||||
print(f"Error decoding JWT: {e}")
|
||||
return redirect(f"/error?msg=Invalid access token received.")
|
||||
|
||||
|
||||
@oauth_bp.route("/logout", methods=["GET", "POST"])
|
||||
def logout():
|
||||
"""登出接口"""
|
||||
# 清除 session
|
||||
session.clear()
|
||||
# print('logout66677')
|
||||
# 返回标准的 JSON 响应
|
||||
return jsonify({"code": 200, "message": "退出登录成功", "data": {}}), 200
|
||||
|
||||
|
||||
@oauth_bp.route("/login")
|
||||
def authorization():
|
||||
"""
|
||||
授权入口。
|
||||
将用户浏览器重定向到 SSO 服务器的授权页面。
|
||||
"""
|
||||
# print("login123")
|
||||
CLIENT_ID = current_app.config["CLIENT_ID"]
|
||||
HOST = current_app.config["SS0_HOST"]
|
||||
REDIRECT_URL = current_app.config["REDIRECT_URL"]
|
||||
|
||||
# 1. 构造重定向到 SSO 授权页面的 URL
|
||||
auth_url = (
|
||||
f"{HOST}v1/auth"
|
||||
f"?response_type=code"
|
||||
f"&client_id={CLIENT_ID}"
|
||||
f"&scope=groups+openid+email+profile"
|
||||
f"&redirect_uri={REDIRECT_URL}"
|
||||
)
|
||||
# print(auth_url)
|
||||
# 2. 执行重定向
|
||||
return redirect(auth_url)
|
||||
|
||||
|
||||
@oauth_bp.route("/report")
|
||||
def authorized_test():
|
||||
"""
|
||||
SSO 回调接口。
|
||||
接收 SSO 服务器返回的授权码 (code),并使用它来兑换访问令牌 (access_token)。
|
||||
"""
|
||||
CLIENT_ID = current_app.config["CLIENT_ID"]
|
||||
CLIENT_PASSWORD = current_app.config["CLIENT_PASSWORD"]
|
||||
HOST = current_app.config["SS0_HOST"] # 建议检查是否是笔误(SSO_HOST)
|
||||
REDIRECT_URL = current_app.config["REDIRECT_URL"]
|
||||
DOMAIN = current_app.config["DOMAIN"]
|
||||
|
||||
code = request.args.get("code")
|
||||
state = request.args.get("state", "/dashboard") # 默认值设为/dashboard
|
||||
|
||||
if not code:
|
||||
# return redirect(f"/error?msg=No authorization code provided.")
|
||||
return redirect(f"/incident/error?msg=No authorization code provided.")
|
||||
|
||||
# 1. 准备请求体,用授权码兑换 Token
|
||||
body = {
|
||||
"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
"redirect_uri": REDIRECT_URL,
|
||||
}
|
||||
|
||||
auth_str = f"{CLIENT_ID}:{CLIENT_PASSWORD}"
|
||||
# 2. 准备请求头
|
||||
headers = {
|
||||
"Authorization": "Basic "+base64.b64encode(auth_str.encode("utf-8")).decode("utf-8"),
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
|
||||
try:
|
||||
# 3. 发送 POST 请求到 SSO 服务器的 Token 端点
|
||||
response = requests.post(f"{HOST}v1/token", data=body, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
response_data = response.json()
|
||||
access_token = response_data.get("access_token")
|
||||
|
||||
# print('access_token', access_token)
|
||||
if not access_token:
|
||||
# return redirect(f"/error?msg=Failed to obtain access token. Response: {response_data}")
|
||||
return redirect(f"/incident/error?msg=Failed to obtain access token. Response: {response_data}")
|
||||
|
||||
# 4. 解析 JWT Token
|
||||
decoded_jwt = jwt.decode(access_token, options={"verify_signature": False})
|
||||
|
||||
# 5. 验证和清理 state 参数 (防止开放重定向攻击)
|
||||
safe_state = "/callback"
|
||||
print(f"Redirecting to: {DOMAIN}{safe_state}")
|
||||
|
||||
# 6. 设置 Cookie
|
||||
resp = make_response(redirect(f"{DOMAIN}{safe_state}"))
|
||||
|
||||
# 关键:设置 Cookie 域名和属性
|
||||
domain = ".localhost" # 让所有 localhost 子域都能读取
|
||||
|
||||
# 设置 access_token
|
||||
resp.set_cookie(
|
||||
"authorization",
|
||||
access_token,
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False, # 前端需要读取
|
||||
samesite="Lax", # 允许跨站
|
||||
secure=False,
|
||||
) # 开发环境不需要HTTPS
|
||||
|
||||
# 设置登录名
|
||||
login_name = decoded_jwt.get("sub", "")
|
||||
resp.set_cookie(
|
||||
"loginName",
|
||||
login_name,
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False,
|
||||
samesite="Lax",
|
||||
secure=False,
|
||||
)
|
||||
|
||||
# 设置用户名 (直接存储,不需要Base64编码)
|
||||
user_name = decoded_jwt.get("name", "")
|
||||
print("user_name", user_name)
|
||||
resp.set_cookie(
|
||||
"userName",
|
||||
user_name, # 直接存储,前端处理编码
|
||||
max_age=3500,
|
||||
path="/",
|
||||
domain=domain,
|
||||
httponly=False,
|
||||
samesite="Lax",
|
||||
secure=False,
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error exchanging code for token: {e}")
|
||||
return redirect(f"/error?msg=Error communicating with SSO server.")
|
||||
except jwt.InvalidTokenError as e:
|
||||
print(f"Error decoding JWT: {e}")
|
||||
return redirect(f"/error?msg=Invalid access token received.")
|
||||
Reference in New Issue
Block a user