适用人群:后端/全栈开发者
难度:中等
预计学习时间:10-15小时
认证方案对比
| 方案 | 原理 | 适用场景 | 优点 | 缺点 |
|---|
| Session | 服务端存储会话 | 传统Web应用 | 简单、可控 | 需要共享存储 |
| JWT | 客户端持有Token | API/前后端分离 | 无状态、跨域 | 无法主动失效 |
| OAuth 2.0 | 第三方授权 | 社交登录 | 安全、标准 | 实现复杂 |
| API Key | 简单令牌 | 开放API | 简单 | 安全性低 |
JWT 实现
# Python Flask + JWT
import jwt
from datetime import datetime, timedelta
from functools import wraps
SECRET_KEY = 'your-secret-key'
def generate_token(user_id: int) -> str:
payload = {
'user_id': user_id,
'exp': datetime.utcnow() + timedelta(days=7),
'iat': datetime.utcnow()
}
return jwt.encode(payload, SECRET_KEY, algorithm='HS256')
def verify_token(token: str) -> dict:
try:
return jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({'error': 'Missing token'}), 401
payload = verify_token(token)
if not payload:
return jsonify({'error': 'Invalid token'}), 401
request.user_id = payload['user_id']
return f(*args, **kwargs)
return decorated
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
if user and user.check_password(data['password']):
token = generate_token(user.id)
return jsonify({'token': token, 'user': user.to_dict()})
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/api/profile')
@login_required
def profile():
user = User.query.get(request.user_id)
return jsonify(user.to_dict())
// Node.js Express + JWT
const jwt = require('jsonwebtoken');
const SECRET = 'your-secret-key';
function generateToken(userId) {
return jwt.sign({ userId }, SECRET, { expiresIn: '7d' });
}
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Missing token' });
try {
const decoded = jwt.verify(token, SECRET);
req.userId = decoded.userId;
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !await bcrypt.compare(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = generateToken(user._id);
res.json({ token, user: { id: user._id, name: user.name, email } });
});
app.get('/api/profile', authMiddleware, async (req, res) => {
const user = await User.findById(req.userId).select('-password');
res.json(user);
});
// PHP JWT (firebase/php-jwt)
// composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secret_key = 'your-secret-key';
function generateToken($user_id) {
global $secret_key;
$payload = [
'user_id' => $user_id,
'exp' => time() + (7 * 24 * 60 * 60), // 7天
'iat' => time()
];
return JWT::encode($payload, $secret_key, 'HS256');
}
function verifyToken() {
global $secret_key;
$header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = str_replace('Bearer ', '', $header);
if (empty($token)) {
http_response_code(401);
echo json_encode(['error' => 'Missing token']);
exit;
}
try {
$decoded = JWT::decode($token, new Key($secret_key, 'HS256'));
return $decoded->user_id;
} catch (Exception $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid token']);
exit;
}
}
OAuth 2.0 社交登录
// Next.js + NextAuth.js
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
import GoogleProvider from 'next-auth/providers/google'
import CredentialsProvider from 'next-auth/providers/credentials'
const handler = NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID!,
clientSecret: process.env.GOOGLE_SECRET!
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
const user = await db.user.findUnique({
where: { email: credentials.email }
})
if (user && await bcrypt.compare(credentials.password, user.password)) {
return { id: user.id, name: user.name, email: user.email }
}
return null
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) token.id = user.id
return token
},
async session({ session, token }) {
session.user.id = token.id
return session
}
}
})
export { handler as GET, handler as POST }
安全最佳实践
1. HTTPS 必须开启
2. 密码必须加盐哈希(bcrypt)
3. JWT有效期不要太长(建议7天以内)
4. 敏感操作要求重新验证密码
5. 登录失败限制次数(防暴力破解)
6. 使用HttpOnly Cookie存储Token(防XSS)
7. 实现刷新Token机制
8. 记录登录日志