在微服务架构日渐流行的今天,如何保障用户在多个应用间无缝切换,避免重复登录的繁琐体验,成为了一个亟待解决的问题。**单点登录(SSO)**正是解决这一问题的有效方案。本文将带你用 Python 实现一个简化但可运行的单点登录系统,深入了解其原理和实现。
SSO 原理深度剖析
SSO 的核心思想是用户只需在一个地方登录一次,就可以访问所有相互信任的应用系统。其背后涉及的关键组件包括:
- 认证服务器(Authorization Server): 负责用户身份的认证和授权,颁发 Token。
- 应用服务器(Application Server): 受保护的资源所在的服务器,需要验证 Token 以确定用户身份。
- Cookie: 用于在浏览器端存储用户的登录状态,在多个应用间传递认证信息。
简化版 SSO 的认证流程如下:
- 用户访问应用 A。
- 应用 A 发现用户未登录,重定向到认证服务器。
- 用户在认证服务器登录。
- 认证服务器验证用户身份,生成 Token,并将 Token 写入 Cookie,然后重定向回应用 A。
- 应用 A 验证 Cookie 中的 Token,如果有效,则认为用户已登录。
- 用户后续访问其他应用(如应用 B),应用 B 重复步骤 2-5,但由于 Cookie 中已存在 Token,用户无需再次登录。
关键技术栈选型:Flask、Redis 与 JWT
- Flask: 轻量级的 Python Web 框架,上手简单,非常适合快速搭建 SSO 系统。
- Redis: 用于存储用户登录状态和 Token,具有高性能和可扩展性。
- JWT (JSON Web Token): 一种安全的 Token 格式,包含用户信息和签名,可以防止篡改。
Python 代码实现简化 SSO
下面是一个用 Python 实现的简化 SSO 系统的示例代码,包括认证服务器和应用服务器两部分。
认证服务器 (auth_server.py)
from flask import Flask, request, redirect, make_response
import jwt
import datetime
import redis
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key' # 请替换为更安全的密钥
redis_client = redis.Redis(host='localhost', port=6379, db=0)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
# 实际项目中需要验证用户名和密码的正确性
if username == 'test' and password == 'test':
payload = {
'username': username,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # Token 过期时间
}
token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
# 将 Token 存储在 Redis 中,并设置过期时间
redis_client.set(username, token, ex=1800)
resp = make_response(redirect('/'))
resp.set_cookie('sso_token', token, httponly=True)
return resp
else:
return 'Invalid username or password'
return '''
<form method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<button type="submit">Login</button>
</form>
'''
@app.route('/')
def index():
return 'Login successful!'
if __name__ == '__main__':
app.run(debug=True, port=5000)
应用服务器 (app_server.py)
from flask import Flask, request, redirect
import jwt
import redis
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key' # 请替换为更安全的密钥,与认证服务器保持一致
redis_client = redis.Redis(host='localhost', port=6379, db=0)
@app.route('/')
def index():
token = request.cookies.get('sso_token')
if not token:
return redirect('http://localhost:5000/login') # 重定向到认证服务器
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
username = payload['username']
# 从 Redis 中验证 Token 是否有效
stored_token = redis_client.get(username)
if stored_token and stored_token.decode('utf-8') == token:
return f'Welcome, {username}!'
else:
return redirect('http://localhost:5000/login')
except jwt.ExpiredSignatureError:
return redirect('http://localhost:5000/login')
except jwt.InvalidTokenError:
return redirect('http://localhost:5000/login')
if __name__ == '__main__':
app.run(debug=True, port=5001)
部署与运行
- 安装依赖:
pip install flask jwt redis - 启动 Redis 服务器。
- 分别运行
auth_server.py(端口 5000) 和app_server.py(端口 5001)。 - 在浏览器中访问
http://localhost:5001/,将会重定向到认证服务器的登录页面。登录成功后,将会显示欢迎信息。
实战避坑经验总结
- 安全性: 密钥 (SECRET_KEY) 的安全性至关重要,务必使用高强度随机密钥,并妥善保管。在生产环境中,建议使用 HTTPS 协议,防止 Token 被窃取。
- Token 过期时间: 合理设置 Token 的过期时间,以平衡安全性和用户体验。
- Redis 持久化: 考虑 Redis 的持久化方案 (RDB 或 AOF),防止数据丢失。
- 分布式部署: 在分布式环境中,需要考虑 Session 共享的问题,可以使用 Redis 集群或类似方案。
- 防止 CSRF 攻击: 在 POST 请求中添加 CSRF Token,防止跨站请求伪造攻击。
通过以上步骤,我们成功实现了一个简化的 Python 单点登录系统。虽然这个示例比较简单,但它涵盖了 SSO 的核心原理和实现方式。在实际项目中,可以根据具体需求进行扩展和优化,例如支持 OAuth 2.0 协议、增加用户管理功能等。同时,在服务器层面,可以考虑使用 Nginx 作为反向代理,利用其强大的负载均衡能力,提高系统的并发连接数和可用性。如果使用宝塔面板管理服务器,可以更方便地配置 Nginx 和 SSL 证书,提升开发效率。
冠军资讯
脱发程序员