在Golang开发的后端系统中,用户认证和授权是保障接口安全的基础能力,需要同时处理用户身份校验、登录状态维持以及不同角色的权限管控。实现这套能力需要结合密码加密、令牌生成、中间件拦截等技术点,下面逐步讲解完整的实现方案。

核心概念说明
首先需要明确几个核心概念的区别:
- 用户认证:验证用户身份的真实性,确认当前请求的用户是系统注册用户,常见方式是账号密码校验。
- 用户授权:在认证通过后,判断用户是否具备访问某个接口或执行某个操作的权限,通常和角色体系绑定。
- 登录状态管理:记录用户的登录状态,避免用户每次请求都需要重新输入密码,常见方案是令牌机制。
用户认证实现
密码加密存储
用户注册时不能直接存储明文密码,需要使用哈希算法加密,Golang中可以使用bcrypt库实现:
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
// 加密密码
func hashPassword(password string) (string, error) {
// 生成哈希密码,第二个参数是成本值,数值越高越安全但耗时越长
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// 校验密码
func checkPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func main() {
password := "user123456"
hash, _ := hashPassword(password)
fmt.Println("加密后的密码:", hash)
// 校验密码是否正确
match := checkPasswordHash(password, hash)
fmt.Println("密码校验结果:", match)
}
登录接口实现
登录接口需要接收用户账号密码,校验通过后生成登录凭证:
package main
import (
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
)
// 定义JWT密钥
var jwtKey = []byte("my_secret_key")
// 用户结构体
type User struct {
Username string `json:"username"`
Password string `json:"password"`
Role string `json:"role"` // 用户角色,比如admin、user
}
// 生成JWT令牌
func generateToken(user User) (string, error) {
// 设置令牌过期时间
expirationTime := time.Now().Add(24 * time.Hour)
// 定义令牌声明
claims := &jwt.RegisteredClaims{
Subject: user.Username,
ExpiresAt: jwt.NewNumericDate(expirationTime),
Issuer: "user-auth-system",
}
// 添加自定义声明,存储用户角色用于授权
claimsMap := jwt.MapClaims{
"username": user.Username,
"role": user.Role,
"exp": expirationTime.Unix(),
"iss": "user-auth-system",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimsMap)
return token.SignedString(jwtKey)
}
// 登录接口处理函数
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "仅支持POST请求", http.StatusMethodNotAllowed)
return
}
// 这里简化逻辑,实际开发需要从请求体解析参数,从数据库查询用户
var user User
user.Username = r.FormValue("username")
user.Password = r.FormValue("password")
user.Role = "user" // 假设查询到的用户角色是普通用户
// 简化密码校验逻辑,实际需要和数据库存储的哈希密码比对
if user.Username != "test" || user.Password != "123456" {
http.Error(w, "账号或密码错误", http.StatusUnauthorized)
return
}
// 生成令牌
tokenString, err := generateToken(user)
if err != nil {
http.Error(w, "生成令牌失败", http.StatusInternalServerError)
return
}
// 返回令牌给客户端
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"token":"%s"}`, tokenString)
}
登录状态管理
客户端拿到JWT令牌后,需要在后续请求的请求头中携带,服务端通过解析令牌判断用户登录状态:
package main
import (
"fmt"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v5"
)
// 解析JWT令牌
func parseToken(tokenString string) (jwt.MapClaims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 校验签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("异常的签名算法")
}
return jwtKey, nil
})
if err != nil {
return nil, err
}
// 提取声明信息
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("无效的令牌")
}
// 校验登录状态的中间件
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 从请求头获取Authorization字段
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "缺少认证令牌", http.StatusUnauthorized)
return
}
// 令牌格式通常是 Bearer xxx,需要提取令牌部分
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "令牌格式错误", http.StatusUnauthorized)
return
}
// 解析令牌
claims, err := parseToken(parts[1])
if err != nil {
http.Error(w, "令牌无效或已过期", http.StatusUnauthorized)
return
}
// 将用户信息存入请求上下文,后续接口可以直接获取
ctx := r.Context()
ctx = contextWithUser(ctx, claims)
next(w, r.WithContext(ctx))
}
}
权限控制实现
授权功能需要结合用户角色判断,比如管理员可以访问所有接口,普通用户只能访问部分接口,我们可以通过中间件实现权限校验:
package main
import (
"context"
"net/http"
)
// 定义上下文键
type contextKey string
const userContextKey contextKey = "user"
// 将用户信息存入上下文
func contextWithUser(ctx context.Context, claims jwt.MapClaims) context.Context {
return context.WithValue(ctx, userContextKey, claims)
}
// 从上下文获取用户信息
func userFromContext(ctx context.Context) jwt.MapClaims {
user, _ := ctx.Value(userContextKey).(jwt.MapClaims)
return user
}
// 权限校验中间件,传入允许访问的角色列表
func permissionMiddleware(allowedRoles ...string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 先经过登录校验中间件,这里可以直接获取用户信息
userClaims := userFromContext(r.Context())
if userClaims == nil {
http.Error(w, "未登录", http.StatusUnauthorized)
return
}
// 获取用户角色
userRole, ok := userClaims["role"].(string)
if !ok {
http.Error(w, "用户角色信息异常", http.StatusForbidden)
return
}
// 校验角色是否在允许列表中
roleAllowed := false
for _, role := range allowedRoles {
if userRole == role {
roleAllowed = true
break
}
}
if !roleAllowed {
http.Error(w, "权限不足", http.StatusForbidden)
return
}
next(w, r)
}
}
}
完整接口注册示例
最后将各个接口和中间件组合起来,完成完整的认证授权体系:
package main
import (
"fmt"
"net/http"
)
func main() {
// 注册登录接口
http.HandleFunc("/login", loginHandler)
// 普通用户接口,需要登录才能访问
http.HandleFunc("/user/profile", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
userClaims := userFromContext(r.Context())
username := userClaims["username"].(string)
fmt.Fprintf(w, "欢迎访问个人中心,用户:%s", username)
}))
// 管理员接口,需要登录且角色为admin才能访问
http.HandleFunc("/admin/dashboard", authMiddleware(
permissionMiddleware("admin")(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "欢迎访问管理员控制台")
}),
))
// 启动服务
fmt.Println("服务启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
注意事项
- JWT密钥需要妥善保管,不要硬编码在代码中,建议通过环境变量配置。
- 令牌过期时间需要根据业务场景设置,敏感系统可以设置较短的过期时间,同时搭配刷新令牌机制。
- 权限体系可以根据业务需求扩展,比如支持更细粒度的接口级权限、数据级权限。
- 密码加密的成本值需要根据服务器性能调整,避免影响接口响应速度。