HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • SSO与身份认证

    • 单点登录(SSO)与身份认证教程
    • 第1章:认证与授权基础
    • 第2章:单点登录原理与实现
    • 第3章:OAuth 2.0与OpenID Connect
    • 第4章:企业级SSO方案
    • 第5章:微服务认证与安全

第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

维度SessionJWT
存储服务端存储客户端存储
状态有状态无状态
撤销容易困难
扩展性需要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. 重要操作需要重新认证

参考资料

  • JWT RFC 7519
  • OAuth 2.0 RFC 6749
  • bcrypt
  • OWASP Authentication Cheat Sheet
Prev
单点登录(SSO)与身份认证教程
Next
第2章:单点登录原理与实现