1. Oauth2.0

2.Google第三方登录
2.1 跳转链接
https://accounts.google.com/o/oauth2/v2/auth
参数:
client_id,redirect_uri,response_type,scope,state
如:https://accounts.google.com/o/oauth2/v2/auth?client_id=xxxx&redirect_uri=https://pm.ljsea.top/tool/third_party_callback&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile&state=eyJ1dWlkIjoiYzljMGQwYTMtMDdmNy00NjUyLWE3NWItNDJhMzFlZDFlODc0IiwidHlwZSI6ImFkZCIsInByb2plY3QiOiJTQVciLCJwbGF0Zm9ybSI6Imdvb2dsZSIsInVzZXJfaWQiOjF9
使用state带上网站的内的业务处理标识
2.2 通过回调得到的Code换取AccessToken
url: https://www.googleapis.com/oauth2/v4/token
参数:client_id,redirect_uri,scope,grant_type=authorization_code,client_secret,code
该接口post请求,表单参数:
curl -X POST -d "client_id=xxx&client_secret=xxxxx&code=4/0Ab_5qlkwhKlXcj7v5481iZjZ7jACB4rwaf0wnIyZSOtRrMIHP4jP2Erk2IBZqpINpAa3RQ&grant_type=authorization_code&redirect_uri=https://pm.ljsea.top/tool/third_party_callback" https://www.googleapis.com/oauth2/v4/token
获取token
2.3 根据token获取用户信息
url:https://www.googleapis.com/oauth2/v2/userinfo
参数:access_token,请求方式GET
https://www.googleapis.com/oauth2/v2/userinfo?access_token=xxxx
获取用户信息后可进行具体业务信息绑定与授权
3.Golang后端实现
3.1 获取跳转链接
func GetThirdPartyAuthUrl(c *gin.Context) {
platform := c.Query("platform")
uuid := c.Query("uuid")
hType := c.Query("type") //操作类型add,login
var resp proto.GenerateResp
if platform == "" || uuid == "" || hType == "" {
resp.Code = proto.ParameterError
resp.Message = "platform or uuid is empty"
c.JSON(http.StatusOK, resp)
return
}
var state proto.ThirdPartyLoginState
state.UUID = uuid
state.Type = hType
state.Platform = platform
state.Project = "SAW"
if hType == "add" {
//查看是否已经绑定
token := c.Request.Header.Get("token")
if token == "" {
token = c.Query("token")
}
if token == "" {
resp.Code = proto.ParameterError
resp.Message = "token is empty"
c.JSON(http.StatusOK, resp)
return
}
userID, err := service.DecodeJWTToken(token)
if err != nil {
resp.Code = proto.ParameterError
resp.Message = err.Error()
c.JSON(http.StatusOK, resp)
return
}
//需要将uuid绑定在该用户上
worker.SetRedisWithExpire("user_add_platform_"+uuid, strconv.Itoa(userID), time.Minute*9)
state.UserID = userID
}
stateStr, _ := json.Marshal(state)
var respUrl string
//base64编码
stateBase64Str := base64.StdEncoding.EncodeToString(stateStr)
switch platform {
case "qq":
params := url.Values{}
params.Add("response_type", "code")
params.Add("client_id", worker.AppId)
params.Add("state", stateBase64Str)
str := fmt.Sprintf("%s", params.Encode())
respUrl = fmt.Sprintf("%s?%s", proto.QQAuthorizeBaseUrl, str)
case "github":
params := url.Values{}
params.Add("client_id", proto.Config.GITHUB_CLIENT_ID)
params.Add("login", uuid)
params.Add("state", stateBase64Str)
baseUri := proto.GitHuAuthorizeBaseUrl
respUrl = fmt.Sprintf("%s?%s", baseUri, params.Encode())
case "gitee":
params := url.Values{}
params.Add("client_id", proto.Config.GITEE_CLIENT_ID)
//response_type=code
params.Add("response_type", "code")
params.Add("state", stateBase64Str)
params.Add("redirect_uri", "https://pm.ljsea.top/tool/gitee_callback")
baseUri := proto.GiteeAuthorizeBaseUrl
respUrl = fmt.Sprintf("%s?%s", baseUri, params.Encode())
case "google":
params := url.Values{}
params.Add("client_id", worker.GoogleClientID)
params.Add("response_type", "code") //直接返回token
redirectURL := "https://pm.ljsea.top/tool/third_party_callback"
scope := "https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile"
//params.Add("redirect_uri", "https://pm.ljsea.top/tool/third_party_callback")
//params.Add("scope", "https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile")
params.Add("state", stateBase64Str)
baseUri := proto.GoogleAuthorizeBaseUrl
respUrl = fmt.Sprintf("%s?%s", baseUri, params.Encode())
respUrl = fmt.Sprintf("%s&redirect_uri=%s&scope=%s", respUrl, redirectURL, scope)
case "facebook":
params := url.Values{}
params.Add("client_id", worker.FacebookClientID)
params.Add("redirect_uri", "https://pm.ljsea.top/tool/third_party_callback")
params.Add("response_type", "code") //直接返回token
params.Add("state", stateBase64Str)
respUrl = fmt.Sprintf("%s?%s", proto.FacebookAuthorizeBaseUrl, params.Encode())
default:
log.Println("platform not support:", platform)
}
resp.Message = "success"
resp.Code = proto.SuccessCode
resp.Data = respUrl
c.JSON(http.StatusOK, resp)
}
3.2 回调接口
func handleThirdPartyCallback(c *gin.Context) {
var resp proto.GenerateResp
code := c.Query("code") //code
stateBase64Str := c.Query("state") //state
//解析base64
decodedBytes, err := base64.StdEncoding.DecodeString(stateBase64Str)
if err != nil {
fmt.Println("Decoding error:", err)
} else {
decodedStr := string(decodedBytes)
//json解析
var state proto.ThirdPartyLoginState
err = json.Unmarshal([]byte(decodedStr), &state)
log.Println("handle callback state:", decodedStr, "\tcode:", code)
if err != nil {
log.Println("json unmarshal error:", err)
} else {
service.DoThirdPartyCallBack(&state, code)
}
}
resp.Code = 0
resp.Message = "success"
c.JSON(http.StatusOK, resp)
}
3.3 根据Code获取Token及获取用户信息
根据上面使用Golang发起请求即可
3.4 业务处理
func DoGoogleCallBack(state *proto.ThirdPartyLoginState, code string) {
//根据code获取Access Token
tokenResp, err := worker.GetGoogleAccessTokenByCode(code, "https://pm.ljsea.top/tool/third_party_callback", worker.GoogleClientID, worker.GoogleClientSecret)
if tokenResp.AccessToken == "" {
log.Println("get google access token is empty")
return
}
log.Println("get google access token:", tokenResp)
//获取用户信息
userInfo, err := worker.GetGoogleUserInfo(tokenResp.AccessToken)
if err != nil {
log.Println("get google user info error:", err)
return
}
log.Println("get google user info:", userInfo)
var thirdPartyLoginStatus proto.ThirdPartyLoginStatus
thirdPartyLoginStatus.Type = state.Platform
thirdPartyUserInfo := proto.ThirdPartyUserInfo{UserID: userInfo.ID, Name: userInfo.Name, Avatar: userInfo.Picture, Email: userInfo.Email}
HandleThirdPartyLoginStatusV2(state, &thirdPartyLoginStatus, &thirdPartyUserInfo)
//更新redis中的第三方登录状态
thirdPartyLoginStatusStr, _ := json.Marshal(thirdPartyLoginStatus)
log.Println("do handle google callback success, third party login status:", string(thirdPartyLoginStatusStr))
worker.SetRedisWithExpire(state.UUID, string(thirdPartyLoginStatusStr), time.Minute*10)
}
func HandleThirdPartyLoginStatusV2(state *proto.ThirdPartyLoginState, thirdPartyLoginStatus *proto.ThirdPartyLoginStatus, userInfo *proto.ThirdPartyUserInfo) {
if state.Type == "login" {
//根据第三方平台查找用户
thirdPartyUserInfoList := dao.FindThirdPartyUserInfoByThirdPartyID(userInfo.UserID)
if thirdPartyUserInfoList == nil || len(thirdPartyUserInfoList) == 0 {
thirdPartyLoginStatus.Status = proto.ThirdPartyUserNotBinded //未绑定用户
} else {
thirdPartyUserInfo := thirdPartyUserInfoList[0]
//获取用户信息
user := GetUserByIDWithCache(thirdPartyUserInfo.UserID)
if user.ID == 0 {
thirdPartyLoginStatus.Status = proto.ThirdPartyUserNotBinded
log.Println("get user by id error")
} else {
//成功
thirdPartyLoginStatus.Status = proto.SuccessCode
thirdPartyLoginStatus.UserInfo.UserID = int(user.ID)
thirdPartyLoginStatus.UserInfo.Username = user.Name
thirdPartyLoginStatus.UserInfo.Email = user.Email
thirdPartyLoginStatus.UserInfo.Token, _ = GenerateJWTToken(int(user.ID), user.Name)
}
}
} else if state.Type == "add" {
//根据第三方平台查找用户
thirdPartyUserInfoList := dao.FindThirdPartyUserInfoByThirdPartyID(userInfo.UserID)
if thirdPartyUserInfoList != nil && len(thirdPartyUserInfoList) > 0 {
thirdPartyLoginStatus.Status = 3 //已绑定用户
} else {
userIDStr := worker.GetRedis("user_add_platform_" + state.UUID)
if userIDStr == "" {
log.Println("user id is empty")
thirdPartyLoginStatus.Status = 2 //未绑定用户
} else {
//字符串转int
userID, _ := strconv.Atoi(userIDStr)
//根据用户ID获取用户信息
user := GetUserByIDWithCache(userID)
if user.ID == 0 {
thirdPartyLoginStatus.Status = 4 //添加用户信息错误
log.Println("get user by id error")
} else {
//需要创建数据库记录
data := dao.ThirdPartyUserInfo{UserID: userID, ThirdPartyID: userInfo.UserID, ThirdPartyPlatform: state.Platform, ThirdPartyUserAvatar: userInfo.Avatar, ThirdPartyUserName: userInfo.Name, ThirdPartyUserUrl: userInfo.Url, ThirdPartyEmail: userInfo.Email}
uid := dao.CreateThirdPartyUserInfoV2(&data)
if uid == 0 {
log.Println("create third party user info error")
thirdPartyLoginStatus.Status = proto.OperationFailed //操作错误
} else {
//成功
thirdPartyLoginStatus.Status = proto.SuccessCode
thirdPartyLoginStatus.UserInfo.UserID = int(user.ID)
thirdPartyLoginStatus.UserInfo.Username = user.Name
thirdPartyLoginStatus.UserInfo.Email = user.Email
thirdPartyLoginStatus.UserInfo.Token, _ = GenerateJWTToken(int(user.ID), user.Name)
}
}
}
}
} else {
log.Println("DoGithubCallBack state type error:", state.Type)
thirdPartyLoginStatus.Status = proto.ParameterError //参数错误
}
//更新userInfo到数据库
err := dao.UpdateThirdPartyUserInfoByThirdPartyID(userInfo.UserID, state.Platform, userInfo.Name, userInfo.Avatar, userInfo.Url)
if err != nil {
log.Println("update third party user info error:", err)
}
}
