第1章:认证与授权基础
认证 vs 授权
核心区别
认证(Authentication):
定义:验证"你是谁"(Who are you?)
示例:
- 用户名 + 密码登录
- 指纹识别
- 人脸识别
- 短信验证码
结果:确认用户身份
授权(Authorization):
定义:验证"你能做什么"(What can you do?)
示例:
- 普通用户只能查看
- 管理员可以增删改查
- VIP用户可以下载
结果:确定用户权限
形象比喻
认证 = 门卫检查身份证(确认你是谁)
授权 = 门禁系统检查权限(确认你能进哪些房间)
流程:
1. 认证:出示身份证 → 确认身份
2. 授权:刷门禁卡 → 检查权限 → 允许/拒绝进入
典型流程
┌──────────┐
│ 用户 │
└──────────┘
↓ 1. 登录(用户名+密码)
┌──────────────────┐
│ 认证服务器 │
│ │
│ 2. 验证身份 │ ← 认证(Authentication)
│ 3. 生成Token │
└──────────────────┘
↓ 4. 返回Token
┌──────────┐
│ 用户 │
└──────────┘
↓ 5. 携带Token访问资源
┌──────────────────┐
│ 资源服务器 │
│ │
│ 6. 验证Token │
│ 7. 检查权限 │ ← 授权(Authorization)
│ 8. 返回资源 │
└──────────────────┘
Session/Cookie认证
1. 工作原理
┌──────────┐ ┌──────────────┐
│ Browser │ │ Server │
└──────────┘ └──────────────┘
│ │
│ 1. POST /login │
│ {username, password} │
├────────────────────────────────>│
│ │
│ 2. 验证成功│
│ 3. 生成SessionID
│ 4. 存储Session
│ │
│ 5. Set-Cookie: sessionid=abc123 │
│<────────────────────────────────┤
│ │
│ 6. GET /api/profile │
│ Cookie: sessionid=abc123 │
├────────────────────────────────>│
│ │
│ 7. 查找Session│
│ 8. 验证有效性 │
│ │
│ 9. 返回用户信息 │
│<────────────────────────────────┤
│ │
2. Go实现Session认证
session.go(Session管理):
package session
import (
"crypto/rand"
"encoding/hex"
"sync"
"time"
)
// Session结构
type Session struct {
ID string
UserID int
CreatedAt time.Time
ExpiresAt time.Time
}
// SessionManager Session管理器
type SessionManager struct {
sessions map[string]*Session
mu sync.RWMutex
}
// NewSessionManager 创建Session管理器
func NewSessionManager() *SessionManager {
sm := &SessionManager{
sessions: make(map[string]*Session),
}
// 定期清理过期Session
go sm.cleanupExpiredSessions()
return sm
}
// CreateSession 创建Session
func (sm *SessionManager) CreateSession(userID int, duration time.Duration) (*Session, error) {
// 生成随机SessionID
sessionID, err := generateSessionID()
if err != nil {
return nil, err
}
session := &Session{
ID: sessionID,
UserID: userID,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(duration),
}
sm.mu.Lock()
sm.sessions[sessionID] = session
sm.mu.Unlock()
return session, nil
}
// GetSession 获取Session
func (sm *SessionManager) GetSession(sessionID string) (*Session, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
session, exists := sm.sessions[sessionID]
if !exists {
return nil, false
}
// 检查是否过期
if time.Now().After(session.ExpiresAt) {
return nil, false
}
return session, true
}
// DeleteSession 删除Session
func (sm *SessionManager) DeleteSession(sessionID string) {
sm.mu.Lock()
delete(sm.sessions, sessionID)
sm.mu.Unlock()
}
// 生成SessionID
func generateSessionID() (string, error) {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// 清理过期Session
func (sm *SessionManager) cleanupExpiredSessions() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for range ticker.C {
sm.mu.Lock()
for id, session := range sm.sessions {
if time.Now().After(session.ExpiresAt) {
delete(sm.sessions, id)
}
}
sm.mu.Unlock()
}
}
auth_handler.go(认证处理):
package handler
import (
"encoding/json"
"net/http"
"time"
)
type AuthHandler struct {
sessionManager *session.SessionManager
userService *service.UserService
}
func NewAuthHandler(sm *session.SessionManager, us *service.UserService) *AuthHandler {
return &AuthHandler{
sessionManager: sm,
userService: us,
}
}
// Login 登录
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// 1. 验证用户名密码
user, err := h.userService.Authenticate(req.Username, req.Password)
if err != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
// 2. 创建Session(7天有效期)
sess, err := h.sessionManager.CreateSession(user.ID, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to create session", http.StatusInternalServerError)
return
}
// 3. 设置Cookie
http.SetCookie(w, &http.Cookie{
Name: "sessionid",
Value: sess.ID,
Path: "/",
HttpOnly: true, // 防止XSS
Secure: true, // 仅HTTPS传输
SameSite: http.SameSiteStrictMode, // 防止CSRF
MaxAge: 7 * 24 * 3600,
})
// 4. 返回成功
json.NewEncoder(w).Encode(map[string]interface{}{
"message": "Login successful",
"user": map[string]interface{}{
"id": user.ID,
"username": user.Username,
},
})
}
// Logout 登出
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
// 1. 获取SessionID
cookie, err := r.Cookie("sessionid")
if err != nil {
http.Error(w, "Not logged in", http.StatusUnauthorized)
return
}
// 2. 删除Session
h.sessionManager.DeleteSession(cookie.Value)
// 3. 清除Cookie
http.SetCookie(w, &http.Cookie{
Name: "sessionid",
Value: "",
Path: "/",
MaxAge: -1, // 立即过期
HttpOnly: true,
Secure: true,
})
json.NewEncoder(w).Encode(map[string]string{
"message": "Logout successful",
})
}
// AuthMiddleware 认证中间件
func (h *AuthHandler) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 获取SessionID
cookie, err := r.Cookie("sessionid")
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 2. 验证Session
session, exists := h.sessionManager.GetSession(cookie.Value)
if !exists {
http.Error(w, "Invalid or expired session", http.StatusUnauthorized)
return
}
// 3. 将用户信息存入Context
ctx := context.WithValue(r.Context(), "userID", session.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
3. Session优缺点
优点:
安全性高(SessionID不包含敏感信息)
服务端可控(随时撤销Session)
易于实现
缺点:
需要服务端存储(内存/Redis)
分布式环境下需要Session共享
扩展性差
CSRF攻击风险
Token认证(JWT)
1. JWT结构
JWT = Header.Payload.Signature
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOjEsImV4cCI6MTYzMjMyMTYwMH0.
abc123def456...
Header(头部):
{
"alg": "HS256",
"typ": "JWT"
}
Payload(载荷):
{
"userId": 1,
"username": "john",
"exp": 1632321600
}
Signature(签名):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
2. JWT工作流程
┌──────────┐ ┌──────────────┐
│ Client │ │ Server │
└──────────┘ └──────────────┘
│ │
│ 1. POST /login │
│ {username, password} │
├────────────────────────────────>│
│ │
│ 2. 验证成功│
│ 3. 生成JWT│
│ │
│ 4. 返回JWT │
│<────────────────────────────────┤
│ │
│ 5. GET /api/profile │
│ Authorization: Bearer <JWT> │
├────────────────────────────────>│
│ │
│ 6. 验证JWT│
│ 7. 解析Payload
│ │
│ 8. 返回用户信息 │
│<────────────────────────────────┤
│ │
3. Go实现JWT认证
jwt.go(JWT工具):
package jwt
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
// JWT密钥(生产环境从环境变量读取)
jwtSecret = []byte("your-secret-key-change-in-production")
ErrInvalidToken = errors.New("invalid token")
ErrExpiredToken = errors.New("token has expired")
)
// Claims JWT声明
type Claims struct {
UserID int `json:"userId"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// GenerateToken 生成JWT Token
func GenerateToken(userID int, username string, duration time.Duration) (string, error) {
now := time.Now()
expiresAt := now.Add(duration)
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
Issuer: "myapp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// ParseToken 解析JWT Token
func ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
}
return nil, ErrInvalidToken
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, ErrInvalidToken
}
// RefreshToken 刷新Token
func RefreshToken(tokenString string, duration time.Duration) (string, error) {
claims, err := ParseToken(tokenString)
if err != nil && !errors.Is(err, ErrExpiredToken) {
return "", err
}
// 重新生成Token
return GenerateToken(claims.UserID, claims.Username, duration)
}
jwt_auth_handler.go(JWT认证处理):
package handler
import (
"encoding/json"
"net/http"
"strings"
"time"
)
type JWTAuthHandler struct {
userService *service.UserService
}
func NewJWTAuthHandler(us *service.UserService) *JWTAuthHandler {
return &JWTAuthHandler{
userService: us,
}
}
// Login JWT登录
func (h *JWTAuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// 1. 验证用户名密码
user, err := h.userService.Authenticate(req.Username, req.Password)
if err != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
// 2. 生成Access Token(1小时)
accessToken, err := jwt.GenerateToken(user.ID, user.Username, 1*time.Hour)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
// 3. 生成Refresh Token(7天)
refreshToken, err := jwt.GenerateToken(user.ID, user.Username, 7*24*time.Hour)
if err != nil {
http.Error(w, "Failed to generate refresh token", http.StatusInternalServerError)
return
}
// 4. 返回Token
json.NewEncoder(w).Encode(map[string]interface{}{
"accessToken": accessToken,
"refreshToken": refreshToken,
"expiresIn": 3600, // 1小时
"tokenType": "Bearer",
"user": map[string]interface{}{
"id": user.ID,
"username": user.Username,
},
})
}
// RefreshToken 刷新Token
func (h *JWTAuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
var req struct {
RefreshToken string `json:"refreshToken"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// 刷新Access Token
accessToken, err := jwt.RefreshToken(req.RefreshToken, 1*time.Hour)
if err != nil {
http.Error(w, "Invalid or expired refresh token", http.StatusUnauthorized)
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"accessToken": accessToken,
"expiresIn": 3600,
"tokenType": "Bearer",
})
}
// JWTMiddleware JWT认证中间件
func (h *JWTAuthHandler) JWTMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 获取Authorization Header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization header", http.StatusUnauthorized)
return
}
// 2. 解析Bearer Token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
return
}
// 3. 验证Token
claims, err := jwt.ParseToken(parts[1])
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// 4. 将用户信息存入Context
ctx := context.WithValue(r.Context(), "userID", claims.UserID)
ctx = context.WithValue(ctx, "username", claims.Username)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
4. JWT优缺点
优点:
无状态(服务端不存储)
跨域友好
适合分布式系统
移动端友好
缺点:
无法主动撤销(除非加黑名单)
Token较大(包含用户信息)
密钥泄露风险
需要处理Token刷新
5. Session vs JWT
| 维度 | Session | JWT |
|---|---|---|
| 存储 | 服务端存储 | 客户端存储 |
| 状态 | 有状态 | 无状态 |
| 撤销 | 容易 | 困难 |
| 扩展性 | 需要Session共享 | 天然支持 |
| 安全性 | 较高 | 需要注意密钥管理 |
| 适用场景 | 传统Web应用 | 微服务、移动端 |
OAuth 2.0基础
1. 核心角色
┌─────────────────────────────────────────────┐
│ OAuth 2.0角色 │
└─────────────────────────────────────────────┘
1. Resource Owner(资源所有者):
用户,拥有资源的所有权
2. Client(客户端):
第三方应用,想要访问用户资源
3. Authorization Server(授权服务器):
认证用户并颁发Access Token
4. Resource Server(资源服务器):
存储用户资源,验证Access Token
2. 授权码模式(最常用)
┌──────────┐ ┌──────────────┐
│ Client │ │ User │
└──────────┘ └──────────────┘
│ │
│ 1. 请求授权 │
├─────────────────────────────────────────────>│
│ Redirect to: │
│ /authorize? │
│ client_id=xxx& │
│ redirect_uri=http://client.com/callback │
│ │
│ 2. 用户登录并同意│
│ │
┌────────────────────────┐ │
│ Authorization Server │<────────────────────────┤
└────────────────────────┘ │
│ │
│ 3. 返回授权码 │
│ Redirect to: │
│ http://client.com/callback?code=AUTH_CODE │
├─────────────────────────────────────────────>│
│ │
┌──────────┐ │
│ Client │<───────────────────────────────────────┤
└──────────┘ │
│ │
│ 4. 用授权码换取Access Token │
│ POST /token │
│ { │
│ grant_type: "authorization_code", │
│ code: "AUTH_CODE", │
│ client_id: "xxx", │
│ client_secret: "yyy" │
│ } │
├─────────────────────────────────────────────>│
┌────────────────────────┐ │
│ Authorization Server │ │
└────────────────────────┘ │
│ │
│ 5. 返回Access Token │
│ { │
│ access_token: "TOKEN", │
│ token_type: "Bearer", │
│ expires_in: 3600 │
│ } │
│<─────────────────────────────────────────────┤
│ │
3. 四种授权模式
授权码模式(Authorization Code):
适用场景:Web应用(最安全)
流程:授权码 → Access Token
优点:最安全,支持刷新Token
隐式模式(Implicit):
适用场景:SPA单页应用(已废弃)
流程:直接返回Access Token
缺点:不安全,不推荐使用
密码模式(Password):
适用场景:自家应用(信任的客户端)
流程:用户名密码 → Access Token
缺点:需要暴露密码给客户端
客户端模式(Client Credentials):
适用场景:服务间调用(机器对机器)
流程:客户端凭证 → Access Token
特点:无用户参与
RBAC权限模型
1. RBAC核心概念
┌─────────┐ ┌──────┐ ┌────────────┐
│ User │────>│ Role │────>│ Permission │
│(用户) │ │(角色)│ │ (权限) │
└─────────┘ └──────┘ └────────────┘
1:N N:M 1:N
示例:
User: John
└─> Role: Admin
└─> Permission: users:read
└─> Permission: users:write
└─> Permission: users:delete
2. Go实现RBAC
rbac.go:
package rbac
type Permission string
const (
UserRead Permission = "user:read"
UserWrite Permission = "user:write"
UserDelete Permission = "user:delete"
PostRead Permission = "post:read"
PostWrite Permission = "post:write"
)
// Role 角色
type Role struct {
Name string
Permissions []Permission
}
// User 用户
type User struct {
ID int
Name string
Roles []Role
}
// RBAC权限检查器
type RBAC struct {
roles map[string]*Role
}
func NewRBAC() *RBAC {
return &RBAC{
roles: make(map[string]*Role),
}
}
// 添加角色
func (r *RBAC) AddRole(name string, permissions []Permission) {
r.roles[name] = &Role{
Name: name,
Permissions: permissions,
}
}
// 检查用户是否有权限
func (r *RBAC) HasPermission(user *User, permission Permission) bool {
for _, role := range user.Roles {
for _, perm := range role.Permissions {
if perm == permission {
return true
}
}
}
return false
}
// RBAC中间件
func (r *RBAC) RequirePermission(permission Permission) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// 从Context获取用户
user, ok := req.Context().Value("user").(*User)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 检查权限
if !r.HasPermission(user, permission) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, req)
})
}
}
使用示例:
// 初始化RBAC
rbac := rbac.NewRBAC()
// 定义角色
rbac.AddRole("admin", []rbac.Permission{
rbac.UserRead,
rbac.UserWrite,
rbac.UserDelete,
rbac.PostRead,
rbac.PostWrite,
})
rbac.AddRole("user", []rbac.Permission{
rbac.UserRead,
rbac.PostRead,
})
// 使用权限中间件
http.Handle("/users",
rbac.RequirePermission(rbac.UserRead)(
http.HandlerFunc(handleGetUsers),
),
)
http.Handle("/users/delete",
rbac.RequirePermission(rbac.UserDelete)(
http.HandlerFunc(handleDeleteUser),
),
)
密码安全
1. 密码哈希
错误方式:
// 错误:明文存储
password := "123456"
db.Save(password)
// 错误:MD5/SHA1(已不安全)
hash := md5.Sum([]byte(password))
正确方式:
// 正确:bcrypt
package auth
import (
"golang.org/x/crypto/bcrypt"
)
// HashPassword 哈希密码
func HashPassword(password string) (string, error) {
// bcrypt自动加盐,cost=12(计算成本)
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
return string(bytes), err
}
// CheckPassword 验证密码
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
使用示例:
// 注册时
hashedPassword, _ := auth.HashPassword("user_password")
db.Save(User{
Username: "john",
Password: hashedPassword, // 存储哈希后的密码
})
// 登录时
user := db.FindByUsername("john")
if auth.CheckPassword("user_password", user.Password) {
// 密码正确
}
2. 密码强度
package auth
import (
"errors"
"regexp"
"unicode"
)
var (
ErrPasswordTooShort = errors.New("password must be at least 8 characters")
ErrPasswordTooWeak = errors.New("password must contain uppercase, lowercase, number and special char")
)
// ValidatePassword 验证密码强度
func ValidatePassword(password string) error {
// 长度检查
if len(password) < 8 {
return ErrPasswordTooShort
}
var (
hasUpper = false
hasLower = false
hasNumber = false
hasSpecial = false
)
for _, char := range password {
switch {
case unicode.IsUpper(char):
hasUpper = true
case unicode.IsLower(char):
hasLower = true
case unicode.IsNumber(char):
hasNumber = true
case unicode.IsPunct(char) || unicode.IsSymbol(char):
hasSpecial = true
}
}
if !hasUpper || !hasLower || !hasNumber || !hasSpecial {
return ErrPasswordTooWeak
}
return nil
}
面试问答
认证和授权有什么区别?
答案:
认证(Authentication):验证"你是谁"
- 登录时输入用户名密码
- 确认用户身份
- 回答:Who are you?
授权(Authorization):验证"你能做什么"
- 检查用户权限
- 确定能否访问资源
- 回答:What can you do?
形象比喻:
认证 = 门卫检查身份证
授权 = 门禁系统检查权限
Session和JWT各有什么优缺点?如何选择?
答案:
Session:
优点:
安全性高(只存SessionID)
易于撤销
服务端可控
缺点:
需要服务端存储
分布式环境需要Session共享
扩展性差
JWT:
优点:
无状态(无需服务端存储)
跨域友好
适合分布式
移动端友好
缺点:
无法主动撤销
Token较大
需要处理刷新
选择建议:
选择Session:
传统Web应用
单体架构
需要精确控制会话
选择JWT:
微服务架构
RESTful API
移动端应用
跨域场景
如何安全地存储密码?
答案:
错误方式:
// 明文存储
password := "123456"
// MD5/SHA1(彩虹表攻击)
md5Hash := md5.Sum([]byte(password))
// 简单加盐(盐值固定)
hash := sha256.Sum256([]byte(password + "salt"))
正确方式:
// bcrypt(自动加盐,慢哈希)
import "golang.org/x/crypto/bcrypt"
// 存储
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
// 验证
err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
bcrypt优势:
1. 自动加盐(每次生成的哈希都不同)
2. 慢哈希(防止暴力破解)
3. 可调节计算成本(cost参数)
4. 行业标准
如何防止CSRF攻击?
答案:
CSRF攻击原理:
1. 用户登录A站点,浏览器保存Cookie
2. 用户访问恶意站点B
3. B站点包含恶意请求:
<img src="https://a.com/transfer?to=hacker&amount=1000">
4. 浏览器自动携带A站点的Cookie发送请求
5. 攻击成功
防御措施:
1. CSRF Token:
// 生成CSRF Token
func GenerateCSRFToken() string {
bytes := make([]byte, 32)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
// 验证CSRF Token
func ValidateCSRFToken(token string, sessionToken string) bool {
return token == sessionToken
}
// 使用
http.SetCookie(w, &http.Cookie{
Name: "csrf_token",
Value: GenerateCSRFToken(),
HttpOnly: false, // 允许JS读取
})
2. SameSite Cookie:
http.SetCookie(w, &http.Cookie{
Name: "sessionid",
Value: sessionID,
SameSite: http.SameSiteStrictMode, // 防止跨站请求
})
3. 验证Referer/Origin:
func validateOrigin(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://trusted-domain.com"
}
4. JWT(天然防CSRF):
JWT存储在Authorization Header中,
不会自动发送,天然防CSRF
如何实现"记住我"功能?
答案:
方案1:延长Session有效期:
// 记住我:7天
duration := 7 * 24 * time.Hour
// 不记住:30分钟
if !rememberMe {
duration = 30 * time.Minute
}
session, _ := sessionManager.CreateSession(userID, duration)
方案2:双Token机制:
// Access Token:短期(1小时)
accessToken, _ := jwt.GenerateToken(userID, 1*time.Hour)
// Refresh Token:长期(7天)
refreshToken, _ := jwt.GenerateToken(userID, 7*24*time.Hour)
// 存储Refresh Token
http.SetCookie(w, &http.Cookie{
Name: "refresh_token",
Value: refreshToken,
Path: "/",
HttpOnly: true,
Secure: true,
MaxAge: 7 * 24 * 3600,
})
方案3:持久化Token:
// 生成持久化Token
rememberToken := generateSecureToken()
// 存储到数据库
db.Save(RememberToken{
UserID: userID,
Token: hashToken(rememberToken),
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
})
// 设置Cookie
http.SetCookie(w, &http.Cookie{
Name: "remember_token",
Value: rememberToken,
MaxAge: 30 * 24 * 3600,
HttpOnly: true,
Secure: true,
})
安全建议:
1. 限制记住我时长(不超过30天)
2. Token单次使用(使用后重新生成)
3. 绑定设备指纹
4. 重要操作需要重新认证