Cookie和Token详解

163 阅读14分钟

Cookie和Token详解

目录

  1. Cookie基础概念
  2. Token基础概念
  3. Cookie vs Token对比
  4. 实现原理和机制
  5. 安全性考虑
  6. 在你的CLI工具中的应用
  7. 实战代码示例

1. Cookie基础概念

1.1 什么是Cookie?

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

# 服务器设置Cookie
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure
Set-Cookie: username=john; Max-Age=3600; Domain=.example.com

# 浏览器发送Cookie
GET /api/user HTTP/1.1
Cookie: sessionid=abc123; username=john

1.2 Cookie的属性

// Cookie属性详解
Set-Cookie: name=value; 
    Domain=.example.com;     // 域名范围
    Path=/;                  // 路径范围
    Expires=Wed, 09 Jun 2021 10:18:14 GMT;  // 过期时间
    Max-Age=3600;           // 有效期(秒)
    Secure;                 // 只在HTTPS传输
    HttpOnly;              // 禁止JavaScript访问
    SameSite=Strict        // 跨站请求限制

1.3 Cookie的类型

会话Cookie (Session Cookie):
  特点: 浏览器关闭后消失
  用途: 临时会话管理
  示例: Set-Cookie: sessionid=abc123

持久Cookie (Persistent Cookie):
  特点: 有明确的过期时间
  用途: 记住用户偏好
  示例: Set-Cookie: theme=dark; Max-Age=86400

安全Cookie (Secure Cookie):
  特点: 只在HTTPS传输
  用途: 敏感信息传输
  示例: Set-Cookie: token=xyz; Secure; HttpOnly

2. Token基础概念

2.1 什么是Token?

Token是一个字符串,用于验证用户身份或授权访问资源。它通常包含用户信息和权限数据。

// Token的基本结构
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200..."
}

2.2 Token的类型

JWT (JSON Web Token)
// JWT结构: Header.Payload.Signature
// Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "1234567890",
  "name": "John Doe", 
  "iat": 1516239022,
  "exp": 1516242622
}

// Signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)
其他Token类型
Opaque Token:
  特点: 不透明字符串,服务器端验证
  格式: "2YotnFZFEjr1zCsicMWpAA"
  用途: 简单的会话管理

Reference Token:
  特点: 指向服务器存储数据的引用
  格式: "550e8400-e29b-41d4-a716-446655440000"
  用途: 高安全性场景

Bearer Token:
  特点: 持有者即可使用
  格式: "Bearer eyJhbGciOiJIUzI1NiI..."
  用途: API访问授权

3. Cookie vs Token详细对比

3.1 核心差异对比表

特性CookieToken
存储位置浏览器自动管理客户端手动管理
传输方式自动携带在请求头手动添加到请求头
跨域支持受同源策略限制无跨域限制
存储大小4KB限制无大小限制
安全性易受CSRF攻击不易受CSRF攻击
移动端支持有限支持完全支持
服务器压力需要服务器存储会话无状态,减少服务器压力

3.2 使用场景对比

Cookie适用场景
# Web应用会话管理示例
from flask import Flask, request, make_response, session

app = Flask(__name__)
app.secret_key = 'your-secret-key'

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    
    if validate_user(username, password):
        # 设置会话Cookie
        session['user_id'] = get_user_id(username)
        session['username'] = username
        
        response = make_response({'status': 'success'})
        
        # 设置额外的Cookie
        response.set_cookie(
            'remember_me', 
            'true', 
            max_age=30*24*3600,  # 30天
            secure=True,
            httponly=True,
            samesite='Strict'
        )
        
        return response
    
    return {'status': 'error', 'message': 'Invalid credentials'}, 401

@app.route('/dashboard')
def dashboard():
    # Cookie自动携带,无需手动处理
    if 'user_id' not in session:
        return {'error': 'Not authenticated'}, 401
    
    return {'user': session['username'], 'data': 'dashboard_data'}
Token适用场景
# API服务Token认证示例
import jwt
from datetime import datetime, timedelta
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = 'your-jwt-secret'

def generate_token(user_id, username):
    """生成JWT Token"""
    payload = {
        'user_id': user_id,
        'username': username,
        'iat': datetime.utcnow(),
        'exp': datetime.utcnow() + timedelta(hours=1)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

def verify_token(token):
    """验证JWT Token"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

@app.route('/api/login', methods=['POST'])
def api_login():
    username = request.json.get('username')
    password = request.json.get('password')
    
    if validate_user(username, password):
        user_id = get_user_id(username)
        token = generate_token(user_id, username)
        
        return {
            'access_token': token,
            'token_type': 'Bearer',
            'expires_in': 3600
        }
    
    return {'error': 'Invalid credentials'}, 401

@app.route('/api/user')
def get_user():
    # 手动处理Token
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        return {'error': 'Missing or invalid token'}, 401
    
    token = auth_header.split(' ')[1]
    payload = verify_token(token)
    
    if not payload:
        return {'error': 'Invalid or expired token'}, 401
    
    return {
        'user_id': payload['user_id'],
        'username': payload['username']
    }

4. 实现原理和机制

4.1 Cookie的工作流程

# Cookie完整工作流程示例
from flask import Flask, request, make_response, session
import uuid
import redis

app = Flask(__name__)
redis_client = redis.Redis(host='localhost', port=6379, db=0)

class SessionManager:
    """基于Redis的会话管理"""
    
    @staticmethod
    def create_session(user_data):
        """创建会话"""
        session_id = str(uuid.uuid4())
        
        # 在Redis中存储会话数据
        session_data = {
            'user_id': user_data['user_id'],
            'username': user_data['username'],
            'created_at': datetime.utcnow().isoformat(),
            'last_accessed': datetime.utcnow().isoformat()
        }
        
        redis_client.setex(
            f"session:{session_id}",
            3600,  # 1小时过期
            json.dumps(session_data)
        )
        
        return session_id
    
    @staticmethod
    def get_session(session_id):
        """获取会话数据"""
        session_data = redis_client.get(f"session:{session_id}")
        if session_data:
            data = json.loads(session_data)
            # 更新最后访问时间
            data['last_accessed'] = datetime.utcnow().isoformat()
            redis_client.setex(
                f"session:{session_id}",
                3600,
                json.dumps(data)
            )
            return data
        return None
    
    @staticmethod
    def destroy_session(session_id):
        """销毁会话"""
        redis_client.delete(f"session:{session_id}")

@app.route('/login', methods=['POST'])
def login():
    """用户登录"""
    username = request.json.get('username')
    password = request.json.get('password')
    
    # 验证用户
    user = authenticate_user(username, password)
    if not user:
        return {'error': 'Invalid credentials'}, 401
    
    # 创建会话
    session_id = SessionManager.create_session({
        'user_id': user['id'],
        'username': user['username']
    })
    
    # 设置Cookie
    response = make_response({'status': 'success'})
    response.set_cookie(
        'session_id',
        session_id,
        max_age=3600,        # 1小时
        secure=True,         # 只在HTTPS传输
        httponly=True,       # 防止XSS
        samesite='Strict'    # 防止CSRF
    )
    
    return response

@app.route('/protected')
def protected():
    """受保护的路由"""
    session_id = request.cookies.get('session_id')
    if not session_id:
        return {'error': 'Not authenticated'}, 401
    
    session_data = SessionManager.get_session(session_id)
    if not session_data:
        return {'error': 'Invalid session'}, 401
    
    return {
        'message': 'Access granted',
        'user': session_data['username']
    }

@app.route('/logout', methods=['POST'])
def logout():
    """用户登出"""
    session_id = request.cookies.get('session_id')
    if session_id:
        SessionManager.destroy_session(session_id)
    
    response = make_response({'status': 'logged out'})
    response.set_cookie('session_id', '', expires=0)  # 清除Cookie
    return response

4.2 Token的工作流程

# JWT Token完整实现
import jwt
import json
import hashlib
from datetime import datetime, timedelta
from cryptography.fernet import Fernet

class TokenManager:
    """Token管理器"""
    
    def __init__(self, secret_key, encryption_key=None):
        self.secret_key = secret_key
        self.encryption_key = encryption_key or Fernet.generate_key()
        self.cipher = Fernet(self.encryption_key)
    
    def generate_access_token(self, user_data, expires_in=3600):
        """生成访问Token"""
        payload = {
            'user_id': user_data['user_id'],
            'username': user_data['username'],
            'roles': user_data.get('roles', []),
            'permissions': user_data.get('permissions', []),
            'iat': datetime.utcnow(),
            'exp': datetime.utcnow() + timedelta(seconds=expires_in),
            'jti': self._generate_jti()  # JWT ID,用于撤销
        }
        
        return jwt.encode(payload, self.secret_key, algorithm='HS256')
    
    def generate_refresh_token(self, user_id):
        """生成刷新Token"""
        payload = {
            'user_id': user_id,
            'type': 'refresh',
            'iat': datetime.utcnow(),
            'exp': datetime.utcnow() + timedelta(days=30)  # 30天
        }
        
        # 刷新Token使用加密
        token = jwt.encode(payload, self.secret_key, algorithm='HS256')
        encrypted_token = self.cipher.encrypt(token.encode()).decode()
        
        return encrypted_token
    
    def verify_access_token(self, token):
        """验证访问Token"""
        try:
            payload = jwt.decode(
                token, 
                self.secret_key, 
                algorithms=['HS256']
            )
            
            # 检查Token是否被撤销
            if self._is_token_revoked(payload.get('jti')):
                return None
            
            return payload
        except jwt.ExpiredSignatureError:
            raise TokenExpiredError("Token已过期")
        except jwt.InvalidTokenError:
            raise TokenInvalidError("Token无效")
    
    def verify_refresh_token(self, encrypted_token):
        """验证刷新Token"""
        try:
            # 解密
            token = self.cipher.decrypt(encrypted_token.encode()).decode()
            
            payload = jwt.decode(
                token,
                self.secret_key,
                algorithms=['HS256']
            )
            
            if payload.get('type') != 'refresh':
                raise TokenInvalidError("不是刷新Token")
            
            return payload
        except Exception:
            raise TokenInvalidError("刷新Token无效")
    
    def refresh_access_token(self, refresh_token):
        """使用刷新Token获取新的访问Token"""
        payload = self.verify_refresh_token(refresh_token)
        user_id = payload['user_id']
        
        # 获取用户最新信息
        user_data = get_user_data(user_id)
        if not user_data:
            raise UserNotFoundError("用户不存在")
        
        return self.generate_access_token(user_data)
    
    def revoke_token(self, token):
        """撤销Token"""
        try:
            payload = jwt.decode(
                token,
                self.secret_key,
                algorithms=['HS256'],
                options={"verify_exp": False}  # 忽略过期检查
            )
            
            jti = payload.get('jti')
            if jti:
                self._add_to_blacklist(jti, payload.get('exp'))
        except jwt.InvalidTokenError:
            pass  # 无效Token无需撤销
    
    def _generate_jti(self):
        """生成JWT ID"""
        import uuid
        return str(uuid.uuid4())
    
    def _is_token_revoked(self, jti):
        """检查Token是否被撤销"""
        # 从Redis或数据库检查黑名单
        return redis_client.exists(f"revoked_token:{jti}")
    
    def _add_to_blacklist(self, jti, exp_timestamp):
        """添加到黑名单"""
        if exp_timestamp:
            ttl = exp_timestamp - datetime.utcnow().timestamp()
            if ttl > 0:
                redis_client.setex(f"revoked_token:{jti}", int(ttl), "1")

# 自定义异常
class TokenExpiredError(Exception):
    pass

class TokenInvalidError(Exception):
    pass

class UserNotFoundError(Exception):
    pass

# 使用示例
token_manager = TokenManager('your-secret-key')

@app.route('/api/login', methods=['POST'])
def api_login():
    """API登录"""
    username = request.json.get('username')
    password = request.json.get('password')
    
    user = authenticate_user(username, password)
    if not user:
        return {'error': 'Invalid credentials'}, 401
    
    # 生成Token对
    access_token = token_manager.generate_access_token(user)
    refresh_token = token_manager.generate_refresh_token(user['user_id'])
    
    return {
        'access_token': access_token,
        'refresh_token': refresh_token,
        'token_type': 'Bearer',
        'expires_in': 3600
    }

@app.route('/api/refresh', methods=['POST'])
def refresh_token():
    """刷新Token"""
    refresh_token = request.json.get('refresh_token')
    if not refresh_token:
        return {'error': 'Missing refresh token'}, 400
    
    try:
        new_access_token = token_manager.refresh_access_token(refresh_token)
        return {
            'access_token': new_access_token,
            'token_type': 'Bearer',
            'expires_in': 3600
        }
    except (TokenInvalidError, UserNotFoundError) as e:
        return {'error': str(e)}, 401

@app.route('/api/logout', methods=['POST'])
def api_logout():
    """API登出"""
    auth_header = request.headers.get('Authorization')
    if auth_header and auth_header.startswith('Bearer '):
        token = auth_header.split(' ')[1]
        token_manager.revoke_token(token)
    
    return {'status': 'logged out'}

5. 安全性考虑

5.1 Cookie安全措施

# Cookie安全配置示例
from flask import Flask
import os

app = Flask(__name__)

# 生产环境安全配置
if os.environ.get('FLASK_ENV') == 'production':
    app.config.update(
        SECRET_KEY=os.environ.get('SECRET_KEY'),
        SESSION_COOKIE_SECURE=True,      # 只在HTTPS传输
        SESSION_COOKIE_HTTPONLY=True,    # 防止XSS
        SESSION_COOKIE_SAMESITE='Strict', # 防止CSRF
        PERMANENT_SESSION_LIFETIME=1800   # 30分钟过期
    )

@app.after_request
def after_request(response):
    """设置安全响应头"""
    # 防止点击劫持
    response.headers['X-Frame-Options'] = 'DENY'
    
    # XSS保护
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    # 内容类型嗅探保护
    response.headers['X-Content-Type-Options'] = 'nosniff'
    
    # HSTS (生产环境)
    if request.is_secure:
        response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    return response

# CSRF保护
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)

@app.route('/form')
def form():
    """带CSRF保护的表单"""
    return '''
    <form method="POST" action="/submit">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
        <input type="text" name="data"/>
        <button type="submit">Submit</button>
    </form>
    '''

5.2 Token安全措施

# Token安全实现
import secrets
import hmac
import hashlib
from datetime import datetime, timedelta

class SecureTokenManager:
    """安全的Token管理器"""
    
    def __init__(self):
        self.secret_key = os.environ.get('JWT_SECRET_KEY')
        self.refresh_secret = os.environ.get('REFRESH_SECRET_KEY')
        self.token_blacklist = set()  # 生产中应使用Redis
    
    def generate_secure_token(self, user_data):
        """生成安全Token"""
        # 添加设备指纹
        device_fingerprint = self._get_device_fingerprint(request)
        
        payload = {
            'user_id': user_data['user_id'],
            'username': user_data['username'],
            'device_fp': device_fingerprint,
            'ip_address': request.remote_addr,
            'user_agent': request.headers.get('User-Agent', '')[:100],
            'iat': datetime.utcnow(),
            'exp': datetime.utcnow() + timedelta(minutes=15),  # 短过期时间
            'jti': secrets.token_urlsafe(32)
        }
        
        return jwt.encode(payload, self.secret_key, algorithm='HS256')
    
    def verify_secure_token(self, token):
        """验证安全Token"""
        try:
            payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
            
            # 检查设备指纹
            if payload['device_fp'] != self._get_device_fingerprint(request):
                raise SecurityError("设备指纹不匹配")
            
            # 检查IP地址(可选,考虑移动用户)
            if payload['ip_address'] != request.remote_addr:
                self._log_security_event("IP地址变化", payload, request)
            
            # 检查黑名单
            if payload['jti'] in self.token_blacklist:
                raise SecurityError("Token已被撤销")
            
            return payload
            
        except jwt.ExpiredSignatureError:
            raise TokenExpiredError("Token已过期")
        except jwt.InvalidTokenError:
            raise TokenInvalidError("Token无效")
    
    def _get_device_fingerprint(self, request):
        """生成设备指纹"""
        components = [
            request.headers.get('User-Agent', ''),
            request.headers.get('Accept-Language', ''),
            request.headers.get('Accept-Encoding', ''),
            # 注意:不要包含容易变化的header
        ]
        
        fingerprint_string = '|'.join(components)
        return hashlib.sha256(fingerprint_string.encode()).hexdigest()[:16]
    
    def _log_security_event(self, event_type, payload, request):
        """记录安全事件"""
        security_log = {
            'event': event_type,
            'user_id': payload.get('user_id'),
            'timestamp': datetime.utcnow().isoformat(),
            'ip_address': request.remote_addr,
            'user_agent': request.headers.get('User-Agent'),
            'token_jti': payload.get('jti')
        }
        
        # 发送到安全监控系统
        logger.warning(f"Security Event: {json.dumps(security_log)}")

# 防护中间件
def require_secure_token(f):
    """安全Token装饰器"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            return {'error': 'Missing token'}, 401
        
        token = auth_header.split(' ')[1]
        
        try:
            payload = secure_token_manager.verify_secure_token(token)
            g.current_user = payload
            return f(*args, **kwargs)
        except (TokenExpiredError, TokenInvalidError, SecurityError) as e:
            return {'error': str(e)}, 401
    
    return decorated_function

6. 在你的CLI工具中的应用

基于你的任务管理CLI工具,我们可以添加用户认证功能:

6.1 扩展TaskManager支持用户认证

# 扩展你的demo.py,添加用户认证功能
import hashlib
import jwt
import getpass
from datetime import datetime, timedelta
from pathlib import Path

class AuthenticatedTaskManager(TaskManager):
    """支持用户认证的任务管理器"""
    
    def __init__(self, data_file=None):
        self.auth_file = os.path.expanduser("~/.tasks_auth.json")
        self.token_file = os.path.expanduser("~/.tasks_token")
        self.secret_key = self._get_or_create_secret()
        self.current_user = None
        
        # 检查是否已登录
        if self._load_token():
            super().__init__(data_file)
        else:
            print("🔐 请先登录")
            self._require_authentication()
            super().__init__(data_file)
    
    def _get_or_create_secret(self):
        """获取或创建密钥"""
        secret_file = os.path.expanduser("~/.tasks_secret")
        
        if os.path.exists(secret_file):
            with open(secret_file, 'r') as f:
                return f.read().strip()
        else:
            import secrets
            secret = secrets.token_urlsafe(32)
            with open(secret_file, 'w') as f:
                f.write(secret)
            os.chmod(secret_file, 0o600)  # 只有用户可读写
            return secret
    
    def _hash_password(self, password):
        """密码哈希"""
        return hashlib.sha256(password.encode()).hexdigest()
    
    def _load_users(self):
        """加载用户数据"""
        if os.path.exists(self.auth_file):
            try:
                with open(self.auth_file, 'r') as f:
                    return json.load(f)
            except (json.JSONDecodeError, IOError):
                return {}
        return {}
    
    def _save_users(self, users):
        """保存用户数据"""
        with open(self.auth_file, 'w') as f:
            json.dump(users, f, indent=2)
        os.chmod(self.auth_file, 0o600)
    
    def register_user(self, username, password):
        """用户注册"""
        users = self._load_users()
        
        if username in users:
            print(f"❌ 用户 {username} 已存在")
            return False
        
        users[username] = {
            'password_hash': self._hash_password(password),
            'created_at': self._get_timestamp(),
            'data_file': os.path.expanduser(f"~/.tasks_{username}.json")
        }
        
        self._save_users(users)
        print(f"✅ 用户 {username} 注册成功")
        return True
    
    def login(self, username, password):
        """用户登录"""
        users = self._load_users()
        
        if username not in users:
            print(f"❌ 用户 {username} 不存在")
            return False
        
        user_data = users[username]
        if user_data['password_hash'] != self._hash_password(password):
            print("❌ 密码错误")
            return False
        
        # 生成Token
        token = self._generate_token(username, user_data)
        self._save_token(token)
        
        self.current_user = username
        self.data_file = user_data['data_file']
        
        print(f"✅ 欢迎回来,{username}!")
        return True
    
    def logout(self):
        """用户登出"""
        if os.path.exists(self.token_file):
            os.remove(self.token_file)
        print("👋 已退出登录")
    
    def _generate_token(self, username, user_data):
        """生成JWT Token"""
        payload = {
            'username': username,
            'data_file': user_data['data_file'],
            'iat': datetime.utcnow(),
            'exp': datetime.utcnow() + timedelta(days=7)  # 7天有效期
        }
        
        return jwt.encode(payload, self.secret_key, algorithm='HS256')
    
    def _save_token(self, token):
        """保存Token到文件"""
        with open(self.token_file, 'w') as f:
            f.write(token)
        os.chmod(self.token_file, 0o600)
    
    def _load_token(self):
        """加载并验证Token"""
        if not os.path.exists(self.token_file):
            return False
        
        try:
            with open(self.token_file, 'r') as f:
                token = f.read().strip()
            
            payload = jwt.decode(
                token, 
                self.secret_key, 
                algorithms=['HS256']
            )
            
            self.current_user = payload['username']
            self.data_file = payload['data_file']
            return True
            
        except (jwt.ExpiredSignatureError, jwt.InvalidTokenError, IOError):
            # Token无效,删除文件
            if os.path.exists(self.token_file):
                os.remove(self.token_file)
            return False
    
    def _require_authentication(self):
        """要求用户认证"""
        print("🔐 任务管理系统认证")
        print("-" * 30)
        
        while True:
            choice = input("请选择: (L)登录 / (R)注册 / (Q)退出: ").lower()
            
            if choice == 'q':
                print("👋 再见!")
                sys.exit(0)
            elif choice == 'l':
                username = input("用户名: ")
                password = getpass.getpass("密码: ")
                if self.login(username, password):
                    break
            elif choice == 'r':
                username = input("新用户名: ")
                password = getpass.getpass("新密码: ")
                confirm_password = getpass.getpass("确认密码: ")
                
                if password != confirm_password:
                    print("❌ 密码不匹配")
                    continue
                
                if len(password) < 6:
                    print("❌ 密码至少6位")
                    continue
                
                if self.register_user(username, password):
                    print("请登录...")
            else:
                print("❌ 无效选择")
    
    def add_task(self, title, description="", priority="medium"):
        """添加任务(重写以包含用户信息)"""
        task = {
            "id": len(self.tasks) + 1,
            "title": title,
            "description": description,
            "priority": priority,
            "completed": False,
            "created_at": self._get_timestamp(),
            "user": self.current_user  # 添加用户标识
        }
        self.tasks.append(task)
        self._save_tasks()
        print(f"✅ 任务已添加: {title}")
    
    def stats(self):
        """显示统计信息(包含用户信息)"""
        super().stats()
        print(f"\n👤 当前用户: {self.current_user}")
        print(f"📁 数据文件: {self.data_file}")

# 更新命令行解析器
def create_auth_parser():
    """创建支持认证的命令行解析器"""
    parser = create_parser()  # 使用原有的解析器
    
    # 添加认证相关命令
    subparsers = parser._subparsers._group_actions[0]
    
    # 登出命令
    logout_parser = subparsers.add_parser(
        "logout",
        help="退出登录",
        description="退出当前用户登录"
    )
    
    # 用户信息命令
    whoami_parser = subparsers.add_parser(
        "whoami",
        help="显示当前用户",
        description="显示当前登录的用户信息"
    )
    
    return parser

# 更新主函数
def auth_main():
    """支持认证的主函数"""
    parser = create_auth_parser()
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(1)
    
    # 处理认证相关命令
    if args.command == "logout":
        # 临时创建manager来执行登出
        temp_manager = AuthenticatedTaskManager.__new__(AuthenticatedTaskManager)
        temp_manager.token_file = os.path.expanduser("~/.tasks_token")
        temp_manager.logout()
        return
    
    # 创建认证任务管理器
    task_manager = AuthenticatedTaskManager(args.data_file)
    
    try:
        if args.command == "whoami":
            print(f"👤 当前用户: {task_manager.current_user}")
            print(f"📁 数据文件: {task_manager.data_file}")
        
        elif args.command == "add":
            task_manager.add_task(args.title, args.description, args.priority)
        
        elif args.command == "list":
            task_manager.list_tasks(show_completed=args.all)
        
        elif args.command == "complete":
            task_manager.complete_task(args.task_id)
        
        elif args.command == "delete":
            task_manager.delete_task(args.task_id)
        
        elif args.command == "search":
            task_manager.search_tasks(args.keyword)
        
        elif args.command == "stats":
            task_manager.stats()
        
        else:
            print(f"❌ 未知命令: {args.command}")
            parser.print_help()
            sys.exit(1)
            
    except KeyboardInterrupt:
        print("\n\n👋 再见!")
        sys.exit(0)
    except Exception as e:
        print(f"❌ 发生错误: {e}")
        sys.exit(1)

if __name__ == "__main__":
    # 选择是否使用认证版本
    if "--auth" in sys.argv:
        sys.argv.remove("--auth")
        auth_main()
    else:
        main()

6.2 使用示例

# 使用认证版本
python demo.py --auth

# 首次运行会提示注册/登录
🔐 任务管理系统认证
------------------------------
请选择: (L)登录 / (R)注册 / (Q)退出: r
新用户名: john
新密码: ******
确认密码: ******
✅ 用户 john 注册成功
请登录...
请选择: (L)登录 / (R)注册 / (Q)退出: l
用户名: john
密码: ******
✅ 欢迎回来,john!

# 正常使用命令
python demo.py --auth add "学习JWT" -d "了解Token认证机制" -p high
python demo.py --auth list
python demo.py --auth whoami
python demo.py --auth logout

7. 高级应用场景

7.1 微服务架构中的Token传递

# 微服务Token传递示例
import requests
from flask import Flask, request, g

class ServiceTokenManager:
    """微服务Token管理"""
    
    def __init__(self, service_name, secret_key):
        self.service_name = service_name
        self.secret_key = secret_key
    
    def create_service_token(self, user_token, target_service):
        """为其他服务创建Token"""
        try:
            # 验证用户Token
            user_payload = jwt.decode(
                user_token, 
                self.secret_key, 
                algorithms=['HS256']
            )
            
            # 创建服务间Token
            service_payload = {
                'user_id': user_payload['user_id'],
                'username': user_payload['username'],
                'source_service': self.service_name,
                'target_service': target_service,
                'iat': datetime.utcnow(),
                'exp': datetime.utcnow() + timedelta(minutes=5)  # 短期有效
            }
            
            return jwt.encode(service_payload, self.secret_key, algorithm='HS256')
        
        except jwt.InvalidTokenError:
            return None
    
    def call_service(self, service_url, user_token, data=None):
        """调用其他微服务"""
        service_token = self.create_service_token(
            user_token, 
            service_url.split('/')[2]  # 从URL提取服务名
        )
        
        headers = {
            'Authorization': f'Bearer {service_token}',
            'X-Original-Token': user_token,
            'X-Source-Service': self.service_name
        }
        
        if data:
            return requests.post(service_url, json=data, headers=headers)
        else:
            return requests.get(service_url, headers=headers)

# 在你的CLI工具中使用
class DistributedTaskManager(AuthenticatedTaskManager):
    """分布式任务管理器"""
    
    def __init__(self, data_file=None):
        super().__init__(data_file)
        self.service_token_manager = ServiceTokenManager(
            'task-service', 
            self.secret_key
        )
        self.api_base_url = os.environ.get('TASK_API_URL', 'http://localhost:5000/api')
    
    def sync_to_cloud(self):
        """同步任务到云端"""
        if not self.current_user:
            print("❌ 请先登录")
            return
        
        # 获取当前Token
        with open(self.token_file, 'r') as f:
            user_token = f.read().strip()
        
        try:
            response = self.service_token_manager.call_service(
                f"{self.api_base_url}/sync",
                user_token,
                {'tasks': self.tasks}
            )
            
            if response.status_code == 200:
                print("✅ 任务已同步到云端")
            else:
                print(f"❌ 同步失败: {response.text}")
        
        except requests.RequestException as e:
            print(f"❌ 网络错误: {e}")

7.2 Session存储优化

# 高性能Session存储
import pickle
import zlib
from abc import ABC, abstractmethod

class SessionStore(ABC):
    """Session存储抽象基类"""
    
    @abstractmethod
    def get(self, session_id):
        pass
    
    @abstractmethod
    def set(self, session_id, data, ttl=3600):
        pass
    
    @abstractmethod
    def delete(self, session_id):
        pass

class RedisSessionStore(SessionStore):
    """Redis Session存储"""
    
    def __init__(self, redis_client, compress=True):
        self.redis = redis_client
        self.compress = compress
    
    def get(self, session_id):
        data = self.redis.get(f"session:{session_id}")
        if data:
            if self.compress:
                data = zlib.decompress(data)
            return pickle.loads(data)
        return None
    
    def set(self, session_id, data, ttl=3600):
        serialized_data = pickle.dumps(data)
        if self.compress:
            serialized_data = zlib.compress(serialized_data)
        
        self.redis.setex(f"session:{session_id}", ttl, serialized_data)
    
    def delete(self, session_id):
        self.redis.delete(f"session:{session_id}")

class DatabaseSessionStore(SessionStore):
    """数据库Session存储"""
    
    def __init__(self, db_connection):
        self.db = db_connection
        self._ensure_table()
    
    def _ensure_table(self):
        """确保Session表存在"""
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS sessions (
                session_id VARCHAR(255) PRIMARY KEY,
                data TEXT NOT NULL,
                expires_at TIMESTAMP NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
        self.db.commit()
    
    def get(self, session_id):
        cursor = self.db.execute(
            "SELECT data FROM sessions WHERE session_id = ? AND expires_at > ?",
            (session_id, datetime.utcnow())
        )
        row = cursor.fetchone()
        if row:
            return pickle.loads(row[0].encode())
        return None
    
    def set(self, session_id, data, ttl=3600):
        expires_at = datetime.utcnow() + timedelta(seconds=ttl)
        serialized_data = pickle.dumps(data).decode()
        
        self.db.execute(
            "INSERT OR REPLACE INTO sessions (session_id, data, expires_at) VALUES (?, ?, ?)",
            (session_id, serialized_data, expires_at)
        )
        self.db.commit()
    
    def delete(self, session_id):
        self.db.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,))
        self.db.commit()
    
    def cleanup_expired(self):
        """清理过期Session"""
        self.db.execute("DELETE FROM sessions WHERE expires_at < ?", (datetime.utcnow(),))
        self.db.commit()

总结

Cookie vs Token选择指南

使用Cookie的场景:
 传统Web应用
 需要浏览器自动管理
 同域名下的会话管理
 不需要跨域访问

使用Token的场景:
 API服务和微服务
 移动应用开发
 跨域访问需求
 无状态架构
 前后端分离项目

安全最佳实践

  1. Cookie安全

    • 设置HttpOnly和Secure标志
    • 使用SameSite防止CSRF
    • 定期轮换Session ID
    • 实现会话超时
  2. Token安全

    • 使用强密钥和算法
    • 设置合适的过期时间
    • 实现Token撤销机制
    • 添加设备指纹验证
  3. 通用安全

    • HTTPS传输
    • 输入验证和输出编码
    • 安全日志记录
    • 定期安全审计