Skip to content

Commit 4354f67

Browse files
committed
support sso token callback
Signed-off-by: Patrick Zhao <zhaoyu@koderover.com>
1 parent 80e63ed commit 4354f67

File tree

6 files changed

+176
-4
lines changed

6 files changed

+176
-4
lines changed

pkg/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func SecretKey() string {
9898
return viper.GetString(setting.ENVSecretKey)
9999
}
100100

101+
func SsoTokenSecret() string {
102+
return "fg7ABA8F7ED66E146075AC8F58F596EAC0"
103+
}
104+
101105
func AslanServiceAddress() string {
102106
s := AslanServiceInfo()
103107
return GetServiceAddress(s.Name, s.Port)

pkg/microservice/user/config/consts.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
)
2222

2323
const (
24-
AppState = setting.ProductName + "user"
25-
SystemIdentityType = "system"
26-
OauthIdentityType = "oauth"
27-
FeiShuEmailHost = "smtp.feishu.cn"
24+
AppState = setting.ProductName + "user"
25+
SystemIdentityType = "system"
26+
SsoTokenIdentityType = "sso_token"
27+
OauthIdentityType = "oauth"
28+
FeiShuEmailHost = "smtp.feishu.cn"
2829

2930
UserGroupCacheKeyFormat = "user_group_%s"
3031
)

pkg/microservice/user/core/handler/login/local.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ limitations under the License.
1717
package login
1818

1919
import (
20+
"net/http"
21+
"net/url"
22+
2023
"github.com/gin-gonic/gin"
2124

25+
"github.com/koderover/zadig/v2/pkg/cli/zadig-agent/helper/log"
26+
configbase "github.com/koderover/zadig/v2/pkg/config"
2227
"github.com/koderover/zadig/v2/pkg/microservice/user/core/service/login"
2328
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
2429
)
@@ -40,6 +45,24 @@ func LocalLogin(c *gin.Context) {
4045
ctx.Resp, ctx.RespErr = resp, err
4146
}
4247

48+
func SsoTokenCallback(c *gin.Context) {
49+
ctx := internalhandler.NewContext(c)
50+
defer func() { internalhandler.JSONResponse(c, ctx) }()
51+
52+
ssoToken := c.Query("token")
53+
token, err := login.SsoTokenCallback(ssoToken, ctx.Logger)
54+
if err != nil {
55+
ctx.RespErr = err
56+
return
57+
}
58+
59+
v := url.Values{}
60+
v.Add("token", token)
61+
redirectUrl := configbase.SystemAddress() + "/?" + v.Encode()
62+
log.Infof("[SsoTokenCallback] redirectUrl: %s", redirectUrl)
63+
c.Redirect(http.StatusSeeOther, redirectUrl)
64+
}
65+
4366
type getCaptchaResp struct {
4467
ID string `json:"id"`
4568
Content string `json:"content"`

pkg/microservice/user/core/handler/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (*Router) Inject(router *gin.RouterGroup) {
7878
general.GET("/callback", login.Callback)
7979
general.GET("/login", login.Login)
8080
general.POST("/login", login.LocalLogin)
81+
general.GET("/sso/token-callback", login.SsoTokenCallback)
8182
general.GET("/login-enabled", login.ThirdPartyLoginEnabled)
8283
general.GET("/captcha", login.GetCaptcha)
8384
general.GET("/logout", login.LocalLogout)

pkg/microservice/user/core/service/login/local.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ limitations under the License.
1717
package login
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"time"
2223

24+
"github.com/go-sql-driver/mysql"
2325
"github.com/golang-jwt/jwt"
26+
"github.com/google/uuid"
2427
"github.com/mojocn/base64Captcha"
2528
"github.com/patrickmn/go-cache"
2629
"go.uber.org/zap"
@@ -29,12 +32,14 @@ import (
2932
configbase "github.com/koderover/zadig/v2/pkg/config"
3033
"github.com/koderover/zadig/v2/pkg/microservice/user/config"
3134
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository"
35+
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/models"
3236
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/orm"
3337
"github.com/koderover/zadig/v2/pkg/microservice/user/core/service/common"
3438
"github.com/koderover/zadig/v2/pkg/setting"
3539
"github.com/koderover/zadig/v2/pkg/shared/client/aslan"
3640
"github.com/koderover/zadig/v2/pkg/shared/client/plutusvendor"
3741
zadigCache "github.com/koderover/zadig/v2/pkg/tool/cache"
42+
"github.com/koderover/zadig/v2/pkg/tool/log"
3843
)
3944

4045
type LoginArgs struct {
@@ -271,3 +276,137 @@ func GetCaptcha(logger *zap.SugaredLogger) (string, string, error) {
271276
}
272277
return id, b64s, nil
273278
}
279+
280+
type SsoTokenClaims struct {
281+
UserID string `json:"userId"`
282+
Account string `json:"account"`
283+
jwt.StandardClaims
284+
}
285+
286+
func SsoTokenCallback(tokenString string, logger *zap.SugaredLogger) (string, error) {
287+
parsedToken, err := jwt.ParseWithClaims(tokenString, &SsoTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
288+
// // 防止 alg 混淆攻击,只接受 HS256
289+
// if token.Method != jwt.SigningMethodHS256 {
290+
// return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
291+
// }
292+
return []byte(configbase.SsoTokenSecret()), nil
293+
})
294+
if err != nil {
295+
return "", err
296+
}
297+
298+
claims, ok := parsedToken.Claims.(*SsoTokenClaims)
299+
if !ok || !parsedToken.Valid {
300+
return "", fmt.Errorf("invalid token")
301+
}
302+
303+
log.Infof("[SsoTokenCallback]: userId: %s, account: %s", claims.UserID, claims.Account)
304+
305+
var userLogin *models.UserLogin
306+
identityType := config.SsoTokenIdentityType
307+
user, err := orm.GetUser(claims.UserID, identityType, repository.DB)
308+
if err != nil {
309+
err = fmt.Errorf("SsoTokenLogin get user account:%s error, error msg:%s", claims.UserID, err.Error())
310+
log.Errorf(err.Error())
311+
return "", err
312+
}
313+
if user == nil {
314+
uid, _ := uuid.NewUUID()
315+
user := &models.User{
316+
Name: claims.Account,
317+
Email: fmt.Sprintf("%s-%s@poc.example", claims.UserID, claims.Account),
318+
IdentityType: config.SystemIdentityType,
319+
Account: claims.UserID,
320+
UID: uid.String(),
321+
}
322+
323+
tx := repository.DB.Begin()
324+
defer func() {
325+
if r := recover(); r != nil {
326+
tx.Rollback()
327+
}
328+
}()
329+
err = orm.CreateUser(user, tx)
330+
if err != nil {
331+
tx.Rollback()
332+
logger.Errorf("[SsoTokenCallback] CreateUser :%v error, error msg:%s", user, err.Error())
333+
var mysqlErr *mysql.MySQLError
334+
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
335+
return "", fmt.Errorf("存在相同用户名")
336+
}
337+
return "", fmt.Errorf("创建用户失败, error: %s", err.Error())
338+
}
339+
userLogin := &models.UserLogin{
340+
UID: user.UID,
341+
LastLoginTime: time.Now().Unix(),
342+
LoginId: user.Account,
343+
LoginType: int(config.AccountLoginType),
344+
}
345+
err = orm.CreateUserLogin(userLogin, tx)
346+
if err != nil {
347+
tx.Rollback()
348+
err = fmt.Errorf("[SsoTokenCallback] CreateUserLogin:%v error, error msg:%s", user, err.Error())
349+
log.Errorf(err.Error())
350+
return "", err
351+
}
352+
if tx.Commit().Error != nil {
353+
return "", fmt.Errorf("创建用户登录信息失败, error: %s", tx.Commit().Error)
354+
}
355+
} else {
356+
userLogin, err = orm.GetUserLogin(user.UID, claims.UserID, config.AccountLoginType, repository.DB)
357+
if err != nil {
358+
err = fmt.Errorf("SsoTokenLogin get user:%s user login not exist, error msg:%s", claims.UserID, err.Error())
359+
log.Errorf(err.Error())
360+
return "", err
361+
}
362+
}
363+
364+
if userLogin != nil {
365+
err = CheckSignature(userLogin.LastLoginTime, logger)
366+
if err != nil {
367+
return "", err
368+
}
369+
}
370+
371+
userLogin.LastLoginTime = time.Now().Unix()
372+
err = orm.UpdateUserLogin(userLogin.UID, userLogin, repository.DB)
373+
if err != nil {
374+
err = fmt.Errorf("[SsoTokenCallback] user:%s update user login info error, error msg:%s", claims.UserID, err.Error())
375+
log.Errorf(err.Error())
376+
return "", err
377+
}
378+
379+
systemSettings, err := aslan.New(configbase.AslanServiceAddress()).GetSystemSecurityAndPrivacySettings()
380+
if err != nil {
381+
err = fmt.Errorf("failed to get system security settings, error: %s", err)
382+
log.Errorf(err.Error())
383+
return "", err
384+
}
385+
386+
token, err := CreateToken(&Claims{
387+
Name: user.Name,
388+
UID: user.UID,
389+
Email: user.Email,
390+
PreferredUsername: user.Account,
391+
StandardClaims: jwt.StandardClaims{
392+
Audience: setting.ProductName,
393+
ExpiresAt: time.Now().Add(time.Duration(systemSettings.TokenExpirationTime) * time.Hour).Unix(),
394+
},
395+
FederatedClaims: FederatedClaims{
396+
ConnectorId: user.IdentityType,
397+
UserId: user.Account,
398+
},
399+
})
400+
if err != nil {
401+
err = fmt.Errorf("[SsoTokenCallback] user:%s create token error, error msg:%s", claims.UserID, err.Error())
402+
log.Errorf(err.Error())
403+
return "", err
404+
}
405+
406+
err = zadigCache.NewRedisCache(config.RedisUserTokenDB()).Write(user.UID, token, time.Duration(systemSettings.TokenExpirationTime)*time.Hour)
407+
if err != nil {
408+
logger.Errorf("failed to write token into cache, error: %s\n warn: this will cause login failure", err)
409+
}
410+
411+
return token, nil
412+
}

pkg/microservice/user/core/service/permission/authn.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ func IsPublicURL(reqPath, method string) bool {
136136
return true
137137
}
138138

139+
if realPath == "/api/v1/sso/token-callback" && method == http.MethodGet {
140+
return true
141+
}
142+
139143
if realPath == "/api/v1/captcha" && method == http.MethodGet {
140144
return true
141145
}

0 commit comments

Comments
 (0)