```
feat(login): 添加Bcrypt密码哈希功能并集成用户认证 - 引入golang.org/x/crypto/bcrypt包用于密码哈希处理 - 实现HashPassword函数对密码进行Bcrypt哈希 - 实现CheckPasswordHash函数验证密码与哈希匹配 - 添加示例代码演示密码哈希和验证功能 feat(login): 集成外部用户信息服务 - 实现GetUserInfo方法调用外部服务获取用户信息 - 添加用户信息展示的示例代码 - 集成用户登录验证流程 fix
This commit is contained in:
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"blazing/common/data/xmlres"
|
"blazing/common/data/xmlres"
|
||||||
_ "blazing/contrib/files/local"
|
_ "blazing/contrib/files/local"
|
||||||
@@ -27,6 +28,31 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// 调用方法
|
||||||
|
// userInfo, err := service.GetUserInfo("694425176@qq.com", "qq694425176")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 输出结果
|
||||||
|
// fmt.Printf("用户名: %s\n", userInfo.Data.Attributes.Username)
|
||||||
|
// fmt.Printf("显示名: %s\n", userInfo.Data.Attributes.DisplayName)
|
||||||
|
// fmt.Printf("头像: %s\n", userInfo.Data.Attributes.AvatarUrl)
|
||||||
|
// fmt.Printf("加入时间: %s\n", userInfo.Data.Attributes.JoinTime)
|
||||||
|
// fmt.Printf("金钱: %.2f\n", userInfo.Data.Attributes.Money)
|
||||||
|
// fmt.Printf("用户组: %s\n", userInfo.Included[0].Attributes.NameSingular)
|
||||||
|
// password := "12345678"
|
||||||
|
|
||||||
|
// // 哈希密码
|
||||||
|
// hash, err := HashPassword(password)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// fmt.Println("哈希结果:", hash)
|
||||||
|
// hash = "$2y$10$027xtOgP6yG8HRPP5zpNdO1ay1MzaxIeBDdNt73VgE2WWQqTUYih2"
|
||||||
|
// // 验证密码
|
||||||
|
// match := CheckPasswordHash(password, hash)
|
||||||
|
// fmt.Println("密码匹配:", match)
|
||||||
// service.NewShinyService().Args(53)
|
// service.NewShinyService().Args(53)
|
||||||
//player.TestPureMatrixSplit()
|
//player.TestPureMatrixSplit()
|
||||||
// for _, i := range xmlres.ItemsMAP {
|
// for _, i := range xmlres.ItemsMAP {
|
||||||
@@ -113,3 +139,16 @@ func kick(id int) {
|
|||||||
// fmt.Println(err)
|
// fmt.Println(err)
|
||||||
// }()
|
// }()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HashPassword 对密码进行 Bcrypt 哈希
|
||||||
|
func HashPassword(password string) (string, error) {
|
||||||
|
// bcrypt.DefaultCost = 10,与 Laravel 默认一致
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPasswordHash 验证密码与哈希是否匹配
|
||||||
|
func CheckPasswordHash(password, hash string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type BaseSysUser struct {
|
|||||||
DepartmentID uint `gorm:"column:departmentId;type:bigint;index" json:"departmentId"` // 部门ID
|
DepartmentID uint `gorm:"column:departmentId;type:bigint;index" json:"departmentId"` // 部门ID
|
||||||
|
|
||||||
Username string `gorm:"column:username;type:varchar(100);not null;Index" json:"username"` // 用户名
|
Username string `gorm:"column:username;type:varchar(100);not null;Index" json:"username"` // 用户名
|
||||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"password"` // 密码
|
Password string `gorm:"column:password;type:varchar(255);" json:"password"` // 密码
|
||||||
PasswordV *int32 `gorm:"column:passwordV;type:int;not null;default:1" json:"passwordV"` // 密码版本, 作用是改完密码,让原来的token失效
|
PasswordV *int32 `gorm:"column:passwordV;type:int;not null;default:1" json:"passwordV"` // 密码版本, 作用是改完密码,让原来的token失效
|
||||||
|
|
||||||
HeadImg *string `gorm:"column:headImg;type:varchar(255)" json:"headImg"` // 头像
|
HeadImg *string `gorm:"column:headImg;type:varchar(255)" json:"headImg"` // 头像
|
||||||
|
|||||||
171
modules/base/service/base.bbs.go
Normal file
171
modules/base/service/base.bbs.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenResponse 用来解析第一次请求返回的 JSON
|
||||||
|
type TokenResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
UserID int `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserResponse 最外层响应
|
||||||
|
type UserResponse struct {
|
||||||
|
Data UserData `json:"data"`
|
||||||
|
Included []Group `json:"included"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserData 用户数据
|
||||||
|
type UserData struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Attributes UserAttributes `json:"attributes"`
|
||||||
|
Relationships Relationships `json:"relationships"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAttributes 用户详细属性
|
||||||
|
type UserAttributes struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
AvatarUrl string `json:"avatarUrl"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
JoinTime string `json:"joinTime"`
|
||||||
|
DiscussionCount int `json:"discussionCount"`
|
||||||
|
CommentCount int `json:"commentCount"`
|
||||||
|
CanEdit bool `json:"canEdit"`
|
||||||
|
CanEditCredentials bool `json:"canEditCredentials"`
|
||||||
|
CanEditGroups bool `json:"canEditGroups"`
|
||||||
|
CanDelete bool `json:"canDelete"`
|
||||||
|
LastSeenAt string `json:"lastSeenAt"`
|
||||||
|
Achievements []string `json:"achievements"`
|
||||||
|
Followed *bool `json:"followed"` // 可能为 null
|
||||||
|
FollowerCount int `json:"followerCount"`
|
||||||
|
FollowingCount int `json:"followingCount"`
|
||||||
|
Money float64 `json:"money"`
|
||||||
|
CanEditMoney bool `json:"canEditMoney"`
|
||||||
|
CanSuspend bool `json:"canSuspend"`
|
||||||
|
LastCheckinTime string `json:"lastCheckinTime"`
|
||||||
|
TotalContinuousCheckIn int `json:"totalContinuousCheckIn"`
|
||||||
|
CheckInCompatibleExtensions []string `json:"checkInCompatibleExtensions"`
|
||||||
|
CanCheckin bool `json:"canCheckin"`
|
||||||
|
CanCheckinContinuous bool `json:"canCheckinContinuous"`
|
||||||
|
CanBeFollowed bool `json:"canBeFollowed"`
|
||||||
|
FofUploadUploadCountCurrent int `json:"fof-upload-uploadCountCurrent"`
|
||||||
|
FofUploadUploadCountAll int `json:"fof-upload-uploadCountAll"`
|
||||||
|
BestAnswerCount int `json:"bestAnswerCount"`
|
||||||
|
IsBanned bool `json:"isBanned"`
|
||||||
|
CanBanIP bool `json:"canBanIP"`
|
||||||
|
CanEditNickname bool `json:"canEditNickname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relationships 用户关系
|
||||||
|
type Relationships struct {
|
||||||
|
Groups RelationshipData `json:"groups"`
|
||||||
|
Achievements RelationshipData `json:"achievements"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelationshipData 关系数据
|
||||||
|
type RelationshipData struct {
|
||||||
|
Data []RelationshipItem `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelationshipItem 关系项
|
||||||
|
type RelationshipItem struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group 组信息
|
||||||
|
type Group struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Attributes GroupAttributes `json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupAttributes 组属性
|
||||||
|
type GroupAttributes struct {
|
||||||
|
NameSingular string `json:"nameSingular"`
|
||||||
|
NamePlural string `json:"namePlural"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
IsHidden int `json:"isHidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo 输入用户名和密码,返回用户信息结构体
|
||||||
|
func GetUserInfo(username, password string) (*UserResponse, error) {
|
||||||
|
// 创建带 Cookie 存储的 HTTP 客户端
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建 CookieJar 失败: %w", err)
|
||||||
|
}
|
||||||
|
client := &http.Client{Jar: jar}
|
||||||
|
|
||||||
|
// 1. POST 获取 token
|
||||||
|
tokenURL := "http://bs.seersun.com/api/token"
|
||||||
|
formData := url.Values{}
|
||||||
|
formData.Set("identification", username)
|
||||||
|
formData.Set("password", password)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", tokenURL, strings.NewReader(formData.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("发送请求失败: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 token
|
||||||
|
var tokenResp TokenResponse
|
||||||
|
err = json.Unmarshal(body, &tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析 token 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 CSRF Token
|
||||||
|
csrfToken := resp.Header.Get("X-CSRF-Token")
|
||||||
|
|
||||||
|
// 2. GET 获取该用户的详细信息
|
||||||
|
usersURL := fmt.Sprintf("http://bs.seersun.com/api/users/%d", tokenResp.UserID)
|
||||||
|
req2, err := http.NewRequest("GET", usersURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
req2.Header.Set("Authentication", tokenResp.Token)
|
||||||
|
req2.Header.Set("X-CSRF-Token", csrfToken)
|
||||||
|
|
||||||
|
resp2, err := client.Do(req2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("发送请求失败: %w", err)
|
||||||
|
}
|
||||||
|
defer resp2.Body.Close()
|
||||||
|
|
||||||
|
body2, err := io.ReadAll(resp2.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析用户信息
|
||||||
|
var userResp UserResponse
|
||||||
|
err = json.Unmarshal(body2, &userResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析用户信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userResp, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
@@ -47,15 +49,56 @@ func (s *BaseSysLoginService) Login(ctx context.Context, req *v1.BaseOpenLoginRe
|
|||||||
err = gerror.New("验证码错误")
|
err = gerror.New("验证码错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
md5password, _ := gmd5.Encrypt(password)
|
// 调用方法
|
||||||
|
userInfo, err := GetUserInfo(username, password)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 输出结果
|
||||||
|
// fmt.Printf("用户名: %s\n", userInfo.Data.Attributes.Username)
|
||||||
|
// fmt.Printf("显示名: %s\n", userInfo.Data.Attributes.DisplayName)
|
||||||
|
// fmt.Printf("头像: %s\n", userInfo.Data.Attributes.AvatarUrl)
|
||||||
|
// fmt.Printf("加入时间: %s\n", userInfo.Data.Attributes.JoinTime)
|
||||||
|
// fmt.Printf("金钱: %.2f\n", userInfo.Data.Attributes.Money)
|
||||||
|
// fmt.Printf("用户组: %s\n", userInfo.Included[0].Attributes.NameSingular)
|
||||||
|
md5password, _ := gmd5.Encrypt(password)
|
||||||
var user *model.BaseSysUser
|
var user *model.BaseSysUser
|
||||||
|
|
||||||
|
if userInfo.Data.Attributes.Username != username { //说明没查找到用户
|
||||||
|
//后端添加的账户
|
||||||
cool.DBM(baseSysUser).Where("username=?", username).Where("password=?", md5password).Where("status=?", 1).Scan(&user)
|
cool.DBM(baseSysUser).Where("username=?", username).Where("password=?", md5password).Where("status=?", 1).Scan(&user)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
|
||||||
err = gerror.New("账户或密码不正确~")
|
err = gerror.New("账户或密码不正确~")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
m := cool.DBM(baseSysUser).Where("username=?", username).Where("status=?", 1)
|
||||||
|
m.Scan(&user)
|
||||||
|
if user == nil {
|
||||||
|
//这里实现注册用户
|
||||||
|
//err = gerror.New("账户或密码不正确~")
|
||||||
|
// return
|
||||||
|
|
||||||
|
NewBaseSysUserService().Gen(userInfo.Data.Attributes)
|
||||||
|
m := cool.DBM(baseSysUser).Where("username=?", username).Where("status=?", 1)
|
||||||
|
m.Scan(&user)
|
||||||
|
} else {
|
||||||
|
user.HeadImg = &userInfo.Data.Attributes.AvatarUrl
|
||||||
|
var ttt = *user.PasswordV + 1
|
||||||
|
user.PasswordV = &ttt
|
||||||
|
_, err := m.Save(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cool.CacheManager.Set(ctx, fmt.Sprintf("admin:passwordVersion:%d", user.ID), user.PasswordV, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
result, err = s.generateTokenByUser(ctx, user)
|
result, err = s.generateTokenByUser(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -90,6 +133,7 @@ func (*BaseSysLoginService) Captcha(req *v1.BaseOpenCaptchaReq) (interface{}, er
|
|||||||
// Logout 退出登录
|
// Logout 退出登录
|
||||||
func (*BaseSysLoginService) Logout(ctx context.Context) (err error) {
|
func (*BaseSysLoginService) Logout(ctx context.Context) (err error) {
|
||||||
userId := cool.GetAdmin(ctx).UserId
|
userId := cool.GetAdmin(ctx).UserId
|
||||||
|
|
||||||
cool.CacheManager.Remove(ctx, "admin:department:"+gconv.String(userId))
|
cool.CacheManager.Remove(ctx, "admin:department:"+gconv.String(userId))
|
||||||
cool.CacheManager.Remove(ctx, "admin:perms:"+gconv.String(userId))
|
cool.CacheManager.Remove(ctx, "admin:perms:"+gconv.String(userId))
|
||||||
cool.CacheManager.Remove(ctx, "admin:token:"+gconv.String(userId))
|
cool.CacheManager.Remove(ctx, "admin:token:"+gconv.String(userId))
|
||||||
|
|||||||
@@ -105,6 +105,33 @@ func (s *BaseSysUserService) ModifyAfter(ctx context.Context, method string, par
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (s *BaseSysUserService) Gen(user UserAttributes) (data interface{}, err error) {
|
||||||
|
var (
|
||||||
|
m = cool.DBM(s.Model)
|
||||||
|
)
|
||||||
|
|
||||||
|
lastInsertId, err := m.Data(user).Data(
|
||||||
|
|
||||||
|
g.Map{
|
||||||
|
"username": user.Username,
|
||||||
|
"headImg": user.AvatarUrl,
|
||||||
|
"departmentId": 1,
|
||||||
|
},
|
||||||
|
).InsertAndGetId()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = g.Map{"id": lastInsertId}
|
||||||
|
roleArray := garray.NewArray()
|
||||||
|
roleArray.PushRight(g.Map{
|
||||||
|
"userId": gconv.Uint(lastInsertId),
|
||||||
|
"roleId": gconv.Uint(13),
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = cool.DBM(model.NewBaseSysUserRole()).Fields("userId,roleId").Insert(roleArray)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceAdd 方法 添加用户
|
// ServiceAdd 方法 添加用户
|
||||||
func (s *BaseSysUserService) ServiceAdd(ctx context.Context, req *cool.AddReq) (data interface{}, err error) {
|
func (s *BaseSysUserService) ServiceAdd(ctx context.Context, req *cool.AddReq) (data interface{}, err error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user