package sso import ( "context" "dubbo.apache.org/dubbo-go/v3/common/logger" "encoding/json" "fmt" "github.com/dgrijalva/jwt-go" "github.com/fonchain_enterprise/fonchain-main/api/account" "github.com/fonchain_enterprise/fonchain-main/pkg/cache" "github.com/fonchain_enterprise/fonchain-main/pkg/config" "github.com/fonchain_enterprise/fonchain-main/pkg/e" "github.com/fonchain_enterprise/fonchain-main/pkg/service" "github.com/fonchain_enterprise/fonchain-main/pkg/utils/secret" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/google/uuid" "io/ioutil" "net/http" "reflect" "strings" "time" ) var ( issuer = "https://erp.fontree.cn" authorizationEndpoint = "https://common.szjixun.cn/sso/auth" tokenEndpoint = "https://common.szjixun.cn/sso/token" userinfoEndpoint = "https://common.szjixun.cn/sso/userinfo" jwksUri = "https://common.szjixun.cn/sso/.well-known/jwks.json" ) type ApplicationInfo struct { ClientId string `json:"clientId"` ClientSecret string `json:"clientSecret"` } type UserInfo struct { Sub string `json:"sub"` Name string `json:"name"` GivenName string `json:"given_name"` FamilyName string `json:"family_name"` PreferredUsername string `json:"preferred_username"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` Picture string `json:"picture"` // ...其他字段... } func LoadEnv() { issuer = config.ApiHost authorizationEndpoint = issuer + "/sso/auth" tokenEndpoint = issuer + "/sso/token" userinfoEndpoint = issuer + "/sso/userinfo" jwksUri = issuer + "/sso/.well-known/jwks.json" } func Configuration(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "issuer": issuer, "authorization_endpoint": authorizationEndpoint, "token_endpoint": tokenEndpoint, "userinfo_endpoint": userinfoEndpoint, "jwks_uri": jwksUri, // 你可以添加其他必要的OIDC配置项 }) return } //Auth erp授权 如果没有登陆则跳转登陆的页面 func Auth(c *gin.Context) { // 验证用户登录,并重定向到回调地址,带上授权码code // 这里需要开发者实现用户认证逻辑,并生成授权码 //authCode := "your_generated_auth_code" //db[authCode] = "asdkfljoqeruowerql" //获取cookie 解析 token, err := c.Cookie("token") //次数应该有一个查找,但是我暂时不需要 domainClientId := c.Query("client_id") clientKey := cache.GetSsoClientId(domainClientId) domainClientSecret := cache.RedisClient.Get(clientKey).Val() if domainClientSecret == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "client_id not exist"}) return } fmt.Println("授权页面", token, err) if err != nil { c.Redirect(http.StatusFound, "/sso/login?"+c.Request.URL.RawQuery) return } key := cache.GetSsoAuthHtml() exists := cache.RedisClient.Exists(key).Val() if exists != 1 { b, err := ioutil.ReadFile("./data/static/auth.html") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } fmt.Println(reflect.TypeOf(b)) cache.RedisClient.Set(key, string(b), 300*time.Second) } htmlContent := cache.RedisClient.Get(key).Val() c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(htmlContent)) return } //AuthSuccess 授权通过 func AuthSuccess(c *gin.Context) { // 验证用户登录,并重定向到回调地址,带上授权码code // 这里需要开发者实现用户认证逻辑,并生成授权码 //authCode := "your_generated_auth_code" //db[authCode] = "asdkfljoqeruowerql" //获取cookie 解析 token, err := c.Cookie("token") domainClientId := c.Query("client_id") clientKey := cache.GetSsoClientId(domainClientId) domainClientSecret := cache.RedisClient.Get(clientKey).Val() if domainClientSecret == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "client_id not exist"}) return } if err != nil { //跳转到登陆 c.Redirect(http.StatusFound, "/sso/login?"+c.Request.URL.RawQuery) return } authCode, b := genJwt(token) if b != true { c.Redirect(http.StatusFound, "/sso/login?"+c.Request.URL.RawQuery) return } applicationInfo := ApplicationInfo{ ClientId: domainClientId, ClientSecret: domainClientSecret, } appByte, err := json.Marshal(applicationInfo) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } cache.RedisClient.Set(cache.GetSSOCodeApplication(authCode), string(appByte), 1200*time.Second) c.Redirect(http.StatusFound, c.Query("redirect_uri")+"?code="+authCode+"&state="+c.Query("state")) } func Token(c *gin.Context) { fmt.Println("令牌断电") var appInfo *ApplicationInfo code := c.PostForm("code") fmt.Println("code-------", code) fmt.Println("Body:", c.PostForm("code")) fmt.Println("Body:", c.PostForm("grant_type")) fmt.Println("Body:", c.PostForm("redirect_uri")) if code == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "code is nil"}) return } fmt.Println("应用信息信息", code, cache.GetSSOCodeApplication(code)) appStr := cache.RedisClient.Get(cache.GetSSOCodeApplication(code)).Val() err := json.Unmarshal([]byte(appStr), &appInfo) fmt.Println("应用信息信息", appStr) fmt.Println("应用信息信息", appInfo) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } id, err := cache.RedisClient.Get(cache.GetSSOCode(code)).Int64() fmt.Println(id, err) if err != nil { //c.String(500, "redis没有查找到code error: %s", err.Error()) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 打印Body内容 req := &account.InfoRequest{ ID: uint64(id), } info, err := service.AccountProvider.Info(c, req) if err != nil { //c.String(500, "redis没有查找到code error: %s", err.Error()) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": issuer, // 你的管理系统 URL "sub": getAccountNumber(info.Info.Domain, info.Info.ID), "aud": appInfo.ClientId, // Gitea OAuth 应用的 client_id "exp": time.Now().Add(time.Hour * 72).Unix(), "iat": time.Now().Unix(), }) tokenString, err := token.SignedString([]byte(appInfo.ClientSecret)) // 使用你的密钥对其进行签名 if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"}) return } b, err := json.Marshal(info.Info) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } accessToken := uuid.New().String() cache.RedisClient.Set(cache.GetSsoAccessToken(accessToken), string(b), 6*time.Hour) c.JSON(http.StatusOK, gin.H{ "access_token": accessToken, "token_type": "bearer", "expires_in": 7200, // 建议加上令牌过期时间 "id_token": tokenString, // ID令牌通常包含用户信息的JWT }) return } func SsoUserInfo(c *gin.Context) { var userInfo account.AccountInfo givenName := "" familyName := "" fmt.Println("用户信息") // 校验访问令牌,并返回用户信息 // 这里需要验证访问令牌是否有效 accessToken := c.GetHeader("Authorization") accessToken = strings.Replace(accessToken, "Bearer ", "", 1) fmt.Println(accessToken) key := cache.GetSsoAccessToken(accessToken) fmt.Println(key) b := cache.RedisClient.Get(key).Val() fmt.Println("信息是", b) if b == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_token"}) return } err := json.Unmarshal([]byte(b), &userInfo) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if userInfo.NickName != "" { runes := []rune(userInfo.NickName) familyName = string(runes[0]) givenName = string(runes[1:]) } info := UserInfo{ Sub: getAccountNumber(userInfo.Domain, userInfo.ID), Name: userInfo.NickName, GivenName: familyName, FamilyName: givenName, PreferredUsername: userInfo.EnglishName, Email: userInfo.MailAccount, EmailVerified: true, Picture: userInfo.Avatar, } /* userInfo := UserInfo{ Sub: "248289761001", Name: "Jane Doe", GivenName: "Jane", FamilyName: "Doe", PreferredUsername: "j.doe", Email: "janedoe@example.com", EmailVerified: true, Picture: "http://example.com/janedoe/me.jpg", } */ c.JSON(http.StatusOK, info) return } func LoginHtml(c *gin.Context) { exists := cache.RedisClient.Exists(cache.GetSSoLoginHtml()).Val() if exists != 1 { b, err := ioutil.ReadFile("./data/static/sso.html") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } cache.RedisClient.Set(cache.GetSSoLoginHtml(), string(b), 300*time.Second) } htmlContent := cache.RedisClient.Get(cache.GetSSoLoginHtml()).Val() c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(htmlContent)) return } func Login(c *gin.Context) { var req account.LoginRequest if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil { service.Error(c, e.InvalidParams, err) return } req.Ip = c.ClientIP() res, err := service.AccountProvider.Login(c, &req) if err != nil { service.Error(c, e.InvalidParams, err) return } token, err := secret.CombineSecret(res.Token, "zz", res.Token) if err != nil { service.Error(c, e.Error, err) return } c.SetCookie("token", token, 43200, "/", "", false, true) //c.Redirect(http.StatusFound, c.Query("redirect_uri")+"?code="+c.Query("code")+"&state="+c.Query("state")) service.Success(c, "") return } func genJwt(token string) (string, bool) { //解析token jwtToken, err := secret.GetJwtFromStr(token) if err != nil { return "'", false } req := account.DecryptJwtRequest{ Token: jwtToken, } info, err := service.AccountProvider.DecryptJwt(context.Background(), &req) // 处理 if err != nil { logger.Warn("sso 解密微服务提示错误", err) return "", false } //info.ID = //生成 uuid 插入redis code := uuid.New().String() cache.RedisClient.Set(cache.GetSSOCode(code), info.ID, 600*time.Second) return code, true } func getAccountNumber(domain string, id uint64) string { return fmt.Sprintf("%s_%010d", domain, id) }