package core import ( "context" "crypto/rsa" "encoding/base64" "encoding/json" "errors" "fmt" "html/template" "io" "log" "math/big" "math/rand" "net" "net/http" "os" "strings" "sync" "sync/atomic" "time" "unsafe" "repositories.action2quare.com/ayo/go-ayo/common" "repositories.action2quare.com/ayo/go-ayo/logger" "github.com/golang-jwt/jwt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" firebase "firebase.google.com/go" "firebase.google.com/go/auth" "google.golang.org/api/option" ) var ( CollectionLink = common.CollectionName("link") CollectionAuth = common.CollectionName("auth") CollectionWhitelist = common.CollectionName("whitelist") CollectionService = common.CollectionName("service") CollectionBlock = common.CollectionName("block") CollectionPlatformLoginToken = common.CollectionName("platform_login_token") //-- 각 플랫폼에 로그인 및 권한 받아오는 과정에 사용하는 Key CollectionUserToken = common.CollectionName("usertoken") CollectionGamepotUserInfo = common.CollectionName("gamepot_userinfo") //-- 클라로부터 수집된 gamepot 정보 - server to server로 유효성이 검증되진 않았지만 수집은 한다. CollectionFirebaseUserInfo = common.CollectionName("firebase_userinfo") //-- Firebase UserInfo ) const ( AuthPlatformFirebaseAuth = "firebase" AuthPlatformGamepot = "gamepot" AuthPlatformGoogle = "google" AuthPlatformMicrosoft = "microsoft" AuthPlatformApple = "apple" AuthPlatformTwitter = "twitter" ) const ( sessionTTL = time.Hour sessionTTLDev = 1 * time.Hour ) func SessionTTL() time.Duration { if *common.Devflag { return sessionTTLDev } return sessionTTL } type mongoAuthCell struct { src *common.Authinfo } func init() { if *common.Devflag { hostname, _ := os.Hostname() CollectionLink = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionLink))) CollectionAuth = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionAuth))) CollectionWhitelist = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionWhitelist))) CollectionService = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionService))) CollectionBlock = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionBlock))) CollectionPlatformLoginToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionPlatformLoginToken))) CollectionUserToken = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionUserToken))) CollectionGamepotUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionGamepotUserInfo))) CollectionFirebaseUserInfo = common.CollectionName(fmt.Sprintf("%s-%s", hostname, string(CollectionFirebaseUserInfo))) } } func (ac *mongoAuthCell) ToAuthinfo() *common.Authinfo { if ac.src == nil { logger.Error("mongoAuthCell ToAuthinfo failed. ac.src is nil") } return ac.src } func (ac *mongoAuthCell) ToBytes() []byte { bt, _ := json.Marshal(ac.src) return bt } func makeAuthCollection(mongoClient common.MongoClient, sessionTTL time.Duration) *common.AuthCollection { authcoll := common.MakeAuthCollection(sessionTTL) authcoll.SessionRemoved = func(sk string) { skid, _ := primitive.ObjectIDFromHex(sk) mongoClient.Delete(CollectionAuth, bson.M{ "sk": skid, }) } authcoll.QuerySession = func(sk string, token string) common.AuthinfoCell { skid, _ := primitive.ObjectIDFromHex(sk) var outcell mongoAuthCell err := mongoClient.FindOneAs(CollectionAuth, bson.M{ "sk": skid, }, &outcell.src, options.FindOne().SetHint("skonly")) if err != nil { logger.Error("QuerySession failed :", err) return nil } if outcell.src == nil { return nil } return &outcell } return authcoll } type apiTokenMap struct { sync.Mutex tokenToService map[string]string } func (tm *apiTokenMap) add(token string, serviceCode string) { tm.Lock() defer tm.Unlock() tm.tokenToService[token] = serviceCode } func (tm *apiTokenMap) remove(token string) { tm.Lock() defer tm.Unlock() delete(tm.tokenToService, token) } func (tm *apiTokenMap) get(token string) (code string, exists bool) { tm.Lock() defer tm.Unlock() code, exists = tm.tokenToService[token] return } type maingateConfig struct { Mongo string `json:"maingate_mongodb_url"` SessionTTL int64 `json:"maingate_session_ttl"` Autologin_ttl int64 `json:"autologin_ttl"` RedirectBaseUrl string `json:"redirect_base_url"` GoogleClientId string `json:"google_client_id"` GoogleClientSecret string `json:"google_client_secret"` TwitterOAuthKey string `json:"twitter_oauth_key"` TwitterOAuthSecret string `json:"twitter_oauth_secret"` TwitterCustomerKey string `json:"twitter_customer_key"` TwitterCustomerSecret string `json:"twitter_customer_secret"` AppleCientId string `json:"apple_client_id"` ApplePrivateKey string `json:"apple_privatekey"` AppleServiceId string `json:"apple_service_id"` AppleTeamId string `json:"apple_team_id"` AppleKeyId string `json:"apple_key_id"` MicrosoftClientId string `json:"microsoft_client_id"` MicrosoftClientSecret string `json:"microsoft_client_secret"` GamepotProjectId string `json:"gamepot_project_id"` GamepotLoginCheckAPIURL string `json:"gamepot_logincheckapi_url"` FirebaseAdminSDKCredentialFile string `json:"firebase_admin_sdk_credentialfile"` } type globalAdmins struct { Admins []string `json:"maingate_global_admins"` emails map[string]bool modtime time.Time } func (ga *globalAdmins) parse() { parsed := make(map[string]bool) for _, admin := range ga.Admins { parsed[admin] = true } ga.emails = parsed ga.modtime = common.ConfigModTime() } type servicelist struct { services unsafe.Pointer } func (sl *servicelist) init(total []*serviceDescription) error { next := make(map[string]*serviceDescription) for _, service := range total { next[service.ServiceName] = service } atomic.StorePointer(&sl.services, unsafe.Pointer(&next)) return nil } func (sl *servicelist) add(s *serviceDescription) { ptr := atomic.LoadPointer(&sl.services) src := (*map[string]*serviceDescription)(ptr) next := map[string]*serviceDescription{} for k, v := range *src { next[k] = v } next[s.ServiceName] = s atomic.StorePointer(&sl.services, unsafe.Pointer(&next)) } func (sl *servicelist) get(sn any) *serviceDescription { ptr := atomic.LoadPointer(&sl.services) src := *(*map[string]*serviceDescription)(ptr) switch sn := sn.(type) { case string: return src[sn] case primitive.ObjectID: for _, desc := range src { if desc.Id == sn { return desc } } } return nil } func (sl *servicelist) all() map[string]*serviceDescription { ptr := atomic.LoadPointer(&sl.services) src := (*map[string]*serviceDescription)(ptr) next := map[string]*serviceDescription{} for k, v := range *src { next[k] = v } return next } func (sl *servicelist) remove(uid primitive.ObjectID) (out *serviceDescription) { ptr := atomic.LoadPointer(&sl.services) src := (*map[string]*serviceDescription)(ptr) next := map[string]*serviceDescription{} var targetkey string out = nil for k, v := range *src { next[k] = v if v.Id == uid { targetkey = k out = v } } delete(next, targetkey) atomic.StorePointer(&sl.services, unsafe.Pointer(&next)) return } // Maingate : type Maingate struct { maingateConfig mongoClient common.MongoClient auths *common.AuthCollection services servicelist admins unsafe.Pointer apiTokenToService apiTokenMap tokenEndpoints map[string]string authorizationEndpoints map[string]string userinfoEndpoint map[string]string jwksUri map[string]string webTemplate map[string]*template.Template firebaseAppClient *auth.Client firebaseAppContext context.Context } // New : func New(ctx context.Context) (*Maingate, error) { var config maingateConfig if err := common.LoadConfig(&config); err != nil { return nil, err } var admins globalAdmins if err := common.LoadConfig(&admins); err == nil { admins.parse() } if config.SessionTTL == 0 { config.SessionTTL = 3600 } mg := Maingate{ maingateConfig: config, services: servicelist{}, admins: unsafe.Pointer(&admins), apiTokenToService: apiTokenMap{ tokenToService: make(map[string]string), }, tokenEndpoints: make(map[string]string), authorizationEndpoints: make(map[string]string), userinfoEndpoint: make(map[string]string), jwksUri: make(map[string]string), } err := mg.prepare(ctx) if err != nil { logger.Error("mg.prepare() failed :", err) return nil, err } opt := option.WithCredentialsFile(mg.FirebaseAdminSDKCredentialFile) firebaseApp, err := firebase.NewApp(context.Background(), nil, opt) if err != nil { logger.Error("firebase admin error initializing app failed :", err) return nil, err } mg.firebaseAppContext = ctx mg.firebaseAppClient, err = firebaseApp.Auth(mg.firebaseAppContext) if err != nil { log.Fatalf("error getting Auth client: %v\n", err) } return &mg, nil } func (mg *Maingate) Destructor() { logger.Println("maingate.Destructor") mg.mongoClient.Close() } func (mg *Maingate) discoverOpenIdConfiguration(name string, url string) error { // microsoft open-id configuration discover, err := http.Get(url) if err != nil { return err } defer discover.Body.Close() body, err := io.ReadAll(discover.Body) if err != nil { return err } var endpoints map[string]any if err := json.Unmarshal(body, &endpoints); err != nil { return err } if tokenEndpoint, ok := endpoints["token_endpoint"].(string); ok { mg.tokenEndpoints[name] = tokenEndpoint if !ok { return fmt.Errorf("token_endpoint is missing. %s %s", name, url) } } if authorizationEndpoint, ok := endpoints["authorization_endpoint"].(string); ok { mg.authorizationEndpoints[name] = authorizationEndpoint if !ok { return fmt.Errorf("authorization_endpoint is missing. %s %s", name, url) } } if userinfoEndpoint, ok := endpoints["userinfo_endpoint"].(string); ok { mg.userinfoEndpoint[name] = userinfoEndpoint if !ok { return fmt.Errorf("userinfo_endpoint is missing. %s %s", name, url) } } if jwksUri, ok := endpoints["jwks_uri"].(string); ok { mg.jwksUri[name] = jwksUri if !ok { return fmt.Errorf("jwks_uri is missing. %s %s", name, url) } } return nil } func (mg *Maingate) prepare(context context.Context) (err error) { if err := mg.discoverOpenIdConfiguration(AuthPlatformMicrosoft, "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"); err != nil { return err } if err := mg.discoverOpenIdConfiguration("google", "https://accounts.google.com/.well-known/openid-configuration"); err != nil { return err } mg.webTemplate = make(map[string]*template.Template) mg.webTemplate[AuthPlatformGamepot] = template.Must(template.ParseFiles("www/gamepot.html")) // redis에서 env를 가져온 후에 mg.mongoClient, err = common.NewMongoClient(context, mg.Mongo, "maingate") if err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionAuth, map[string]bson.D{ "skonly": {{Key: "sk", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ "platformuid": {{Key: "platform", Value: 1}, {Key: "uid", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionLink, map[string]bson.D{ "emailplatform": {{Key: "email", Value: 1}, {Key: "platform", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeIndices(CollectionWhitelist, map[string]bson.D{ "service": {{Key: "service", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeExpireIndex(CollectionWhitelist, 10); err != nil { return err } if err = mg.mongoClient.MakeExpireIndex(CollectionAuth, int32(mg.SessionTTL+300)); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionBlock, map[string]bson.D{ "codeaccid": {{Key: "code", Value: 1}, {Key: "accid", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeExpireIndex(CollectionBlock, int32(3)); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionPlatformLoginToken, map[string]bson.D{ "platformauthtoken": {{Key: "platform", Value: 1}, {Key: "key", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeExpireIndex(CollectionPlatformLoginToken, int32(mg.SessionTTL+300)); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionUserToken, map[string]bson.D{ "platformusertoken": {{Key: "platform", Value: 1}, {Key: "userid", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionGamepotUserInfo, map[string]bson.D{ "gamepotuserid": {{Key: "gamepotuserid", Value: 1}}, }); err != nil { return err } if err = mg.mongoClient.MakeUniqueIndices(CollectionFirebaseUserInfo, map[string]bson.D{ "firebaseuserid": {{Key: "firebaseuserid", Value: 1}}, }); err != nil { return err } mg.auths = makeAuthCollection(mg.mongoClient, time.Duration(mg.SessionTTL*int64(time.Second))) go watchAuthCollection(context, mg.auths, mg.mongoClient) go mg.watchWhitelistCollection(context) return nil } func whitelistKey(email string) string { if strings.HasPrefix(email, "*@") { // 도메인 전체 허용 return email[2:] } return email } func (mg *Maingate) RegisterHandlers(ctx context.Context, serveMux *http.ServeMux, prefix string) error { var allServices []*serviceDescription logger.Println(CollectionService) if err := mg.mongoClient.FindAllAs(CollectionService, bson.M{}, &allServices, options.Find().SetReturnKey(false)); err != nil { return err } for _, service := range allServices { if err := service.prepare(mg); err != nil { return err } } logger.Println("RegisterHandlers...") mg.services.init(allServices) for _, service := range allServices { if service.Closed { continue } logger.Println("ServiceCode:", service.ServiceCode) serveMux.Handle(common.MakeHttpHandlerPattern(prefix, service.ServiceCode, "/"), service) } serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "api", "/"), mg.api) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "query", "/"), mg.query) configraw, _ := json.Marshal(mg.maingateConfig) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "config"), func(w http.ResponseWriter, r *http.Request) { apitoken := r.Header.Get("MG-X-API-TOKEN") if len(apitoken) == 0 { logger.Println("MG-X-API-TOKEN is missing") w.WriteHeader(http.StatusBadRequest) return } _, exists := mg.apiTokenToService.get(apitoken) if !exists { logger.Println("MG-X-API-TOKEN is invalid :", apitoken) w.WriteHeader(http.StatusBadRequest) return } w.Write(configraw) }) fsx := http.FileServer(http.Dir("./console")) serveMux.Handle(common.MakeHttpHandlerPattern(prefix, "console", "/"), http.StripPrefix("/console/", fsx)) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGoogle), mg.platform_google_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGoogle), mg.platform_google_authorize) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformGoogle), mg.platform_google_authorize_result) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformMicrosoft), mg.platform_microsoft_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformMicrosoft), mg.platform_microsoft_authorize) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformMicrosoft), mg.platform_microsoft_authorize_result) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformTwitter), mg.platform_twitter_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformTwitter), mg.platform_twitter_authorize) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformTwitter), mg.platform_twitter_authorize_result) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformApple), mg.platform_apple_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformApple), mg.platform_apple_authorize) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_result", AuthPlatformApple), mg.platform_apple_authorize_result) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformGamepot), mg.platform_gamepot_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize", AuthPlatformGamepot), mg.platform_gamepot_authorize) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformGamepot), mg.platform_gamepot_authorize_sdk) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "request_login_url", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_get_login_url) serveMux.HandleFunc(common.MakeHttpHandlerPattern(prefix, "authorize_sdk", AuthPlatformFirebaseAuth), mg.platform_firebaseauth_authorize_sdk) go mg.watchServiceCollection(ctx, serveMux, prefix) // fsx := http.FileServer(http.Dir("console")) // serveMux.Handle("/console/", http.StripPrefix("/console/", fsx)) // logger.Println("console file server open") return nil } func (mg *Maingate) query(w http.ResponseWriter, r *http.Request) { defer func() { s := recover() if s != nil { logger.Error(s) } }() defer func() { io.Copy(io.Discard, r.Body) r.Body.Close() }() queryvals := r.URL.Query() sk := queryvals.Get("sk") if len(sk) == 0 { w.WriteHeader(http.StatusUnauthorized) return } info := mg.auths.Find(sk) if info == nil { logger.Println("session key is not valid :", sk) w.WriteHeader(http.StatusUnauthorized) return } apitoken := r.Header.Get("MG-X-API-TOKEN") if len(apitoken) == 0 { logger.Println("MG-X-API-TOKEN is missing") w.WriteHeader(http.StatusBadRequest) return } servicecode, exists := mg.apiTokenToService.get(apitoken) if !exists { logger.Println("MG-X-API-TOKEN is invalid :", apitoken) w.WriteHeader(http.StatusBadRequest) return } if info.ServiceCode != servicecode { logger.Println("session is not for this service :", info.ServiceCode, servicecode) w.WriteHeader(http.StatusBadRequest) return } bt, _ := json.Marshal(info) w.Write(bt) } func (mg *Maingate) GeneratePlatformLoginNonceKey() string { const allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 52) for i := range b { b[i] = allowed[rand.Intn(len(allowed))] } return string(b) } func (mg *Maingate) GetUserBrowserInfo(r *http.Request) (string, error) { //=========== 브라우저 검증쪽은 앞으로 빼자 host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { logger.Error("Error: RemoteAddr Split Error :", err) } cookie, err := r.Cookie("ActionSquareSessionExtraInfo") if err != nil { return "", err } requestinfo := fmt.Sprintf("%s_%s", cookie.Value, host) return requestinfo, nil } func (mg *Maingate) setUserToken(info usertokeninfo) error { mg.mongoClient.Delete(CollectionUserToken, bson.M{ "platform": info.platform, "userid": info.userid, }) _, _, err := mg.mongoClient.Update(CollectionUserToken, bson.M{ "_id": primitive.NewObjectID(), }, bson.M{ "$setOnInsert": bson.M{ "platform": info.platform, "userid": info.userid, "token": info.token, "secret": info.secret, "brinfo": info.brinfo, "lastupdate": time.Now().Unix(), "accesstoken": info.accesstoken, "accesstoken_expire_time": info.accesstoken_expire_time, }, }, options.Update().SetUpsert(true)) return err } func (mg *Maingate) getUserTokenWithCheck(platform string, userid string, brinfo string) (usertokeninfo, error) { var info usertokeninfo found, err := mg.mongoClient.FindOne(CollectionUserToken, bson.M{ "platform": platform, "userid": userid, "brinfo": brinfo, }) if err != nil { return info, err } if found == nil { return info, errors.New("user token not found: " + platform + " / " + userid + " / " + brinfo) } updatetime, ok := found["lastupdate"].(int64) if !ok || time.Now().Unix()-updatetime < mg.maingateConfig.Autologin_ttl { info.platform = platform info.userid = userid info.brinfo = brinfo token := found["token"] if token != nil { info.token = token.(string) } secret := found["secret"] if secret != nil { info.secret = secret.(string) } accesstoken := found["accesstoken"] if accesstoken != nil { info.accesstoken = accesstoken.(string) } accesstoken_expire_time := found["accesstoken_expire_time"] if accesstoken_expire_time != nil { info.accesstoken_expire_time = accesstoken_expire_time.(int64) } return info, nil } return info, errors.New("session is expired") } func (mg *Maingate) updateUserinfo(info usertokeninfo) (bool, string, string) { var success bool var userid, email string success = false userid = "" email = "" switch info.platform { case AuthPlatformApple: success, userid, email = mg.platform_apple_getuserinfo(info.token) case AuthPlatformTwitter: success, userid, email = mg.platform_twitter_getuserinfo(info.token, info.secret) case AuthPlatformMicrosoft: success, userid, email = mg.platform_microsoft_getuserinfo(info) case AuthPlatformGoogle: success, userid, email = mg.platform_google_getuserinfo(info) case AuthPlatformGamepot: success, userid, email = mg.platform_gamepot_getuserinfo(info) case AuthPlatformFirebaseAuth: success, userid, email = mg.platform_firebase_getuserinfo(info) } if !success { return false, "", "" } if info.userid != userid { logger.Error("userinfo / id is not match. :", info.userid, " / ", userid) return false, "", "" } mg.setUserToken(info) return success, userid, email } func (mg *Maingate) getProviderInfo(platform string, uid string) (error, string, string) { provider := "" providerid := "" switch platform { case AuthPlatformFirebaseAuth: // Fireabase 통해서 로그인 하는 경우, 원래 제공하는 Google 혹은 Apple쪽 ID와 일치 시킨다 found, err := mg.mongoClient.FindOne(CollectionFirebaseUserInfo, bson.M{ "firebaseuserid": uid, }) if err != nil { return err, "", "" } if found == nil { return errors.New("firebase info not found: " + uid), "", "" } provider = found["firebaseprovider"].(string) providerid = found["firebaseproviderId"].(string) if provider == "" || providerid == "" { return errors.New("getProviderInfo - firebase info not found: " + provider + " / " + providerid), "", "" } default: provider = platform providerid = uid } if provider == "" || providerid == "" { return errors.New("getProviderInfo - provider info not found: " + provider + " / " + providerid), "", "" } return nil, provider, providerid } var errKeyNotFound = errors.New("key not found") func downloadSigningCert(keyurl string, kid string, alg string) (rsa.PublicKey, error) { var publicKey rsa.PublicKey resp, err := http.Get(keyurl) // GET 호출 if err != nil { return rsa.PublicKey{}, err } defer func() { io.Copy(io.Discard, resp.Body) resp.Body.Close() }() // 결과 출력 data, err := io.ReadAll(resp.Body) if err != nil { return rsa.PublicKey{}, err } var parseddata map[string]interface{} err = json.Unmarshal([]byte(data), &parseddata) if err != nil { return rsa.PublicKey{}, err } mapKeys, keysok := parseddata["keys"] if !keysok { return rsa.PublicKey{}, errKeyNotFound } keylist, keyok := mapKeys.([]interface{}) if !keyok { return rsa.PublicKey{}, errKeyNotFound } for _, key := range keylist { alg_, alg_ok := key.(map[string]interface{})["alg"] kty_, kty_ok := key.(map[string]interface{})["kty"] if !alg_ok && kty_ok { alg_ = kty_ alg_ok = kty_ok } kid_, kid_ok := key.(map[string]interface{})["kid"] nkey_, nkey_ok := key.(map[string]interface{})["n"] ekey_, ekey_ok := key.(map[string]interface{})["e"] if alg_ok && kid_ok && nkey_ok && ekey_ok { if (alg_.(string) == alg || (alg == "RS256" && alg_ == "RSA")) && kid_.(string) == kid { decode_nkey_, err := base64.RawURLEncoding.DecodeString(nkey_.(string)) if err != nil { return rsa.PublicKey{}, errors.New("n key decode fail") } if err != nil { return rsa.PublicKey{}, errors.New("e key decode fail") } if ekey_.(string) != "AQAB" && ekey_.(string) != "AAEAAQ" { return rsa.PublicKey{}, errors.New("e key is not AQAB or AAEAAQ") } publicKey = rsa.PublicKey{ N: new(big.Int).SetBytes(decode_nkey_), E: 65537, } } } } return publicKey, nil } func JWTparseCode(keyurl string, code string) (string, string, string) { parts := strings.Split(string(code), ".") if len(parts) != 3 { logger.Error("ErrCannotDecode:", len(parts)) return "", "", "" } decoded, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { logger.Error("ErrInvalidTokenPart:", string(decoded)) return "", "", "" } var parseddata map[string]string err = json.Unmarshal(decoded, &parseddata) if err != nil { panic(err) } kid, kidok := parseddata["kid"] alg, algok := parseddata["alg"] if !kidok { logger.Error("ErrorHeader_kid_not_found") return "", "", "" } if !algok { logger.Error("ErrorHeader_alg_not_found") return "", "", "" } publicKey, err := downloadSigningCert(keyurl, kid, alg) if err != nil { logger.Error("ErrorHeader_alg_not_found:", err) return "", "", "" } token, err := jwt.Parse(code, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, errors.New("Unexpected signing method:" + token.Header["alg"].(string)) } return &publicKey, nil }) if err != nil { if err := err.(*jwt.ValidationError); err != nil { if err.Errors == jwt.ValidationErrorExpired { logger.Error("ValidationErrorExpired:", err) return "", "", "" } logger.Error("JWT Validation Error:", err) return "", "", "" } } claims, ok := token.Claims.(jwt.MapClaims) if !ok { logger.Error("JWT token.Claims Fail: - MapClaims") return "", "", "" } // for claim := range claims { // fmt.Println(claim, claims[claim]) // } nonce_, nonchk := claims["nonce"] nonce := "" if nonchk { nonce = nonce_.(string) } email_, emailchk := claims["email"] email := "" if emailchk { email = email_.(string) } //--- nonce 체크 필요하다. return claims["sub"].(string), email, nonce }