[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fzKsQdNHGmVHj998owl1bDg_jkU58gej7oZ6FNnfPSNg":3},{"code":4,"message":5,"data":6},200,"成功",{"id":7,"createdAt":8,"title":9,"content":10,"summary":11,"image":12,"uid":13,"user":14,"categoryId":21,"category":22,"subCategoryId":24,"subCategory":25,"comments":27,"status":17,"reason":28,"notice":28,"visitCount":29,"commentCount":30,"keywords":31},141,"2024-11-28T07:00:21.332Z","web中实现Google登录和firebase登录以及google token转firebase token","## 一、前言\n在web的前端开发中，实现第三方授权登录的场景和平台很多，最常见的比如Google登录、Apple登录、Facebook登录，国内常见的有比如微信登录、支付宝登录，这些都是需要依赖他们这些第三方平台的授权流程才能拿到登录用户的身份id或者access token的。\n\n在web开发中，实现第三方登录的形式，我的经验告诉我大概可以分为3种：\n- 1. 引入第三方平台的库，在vue或者react或者原生div等按钮组件中，加一些标签属性或者配置。当点击后，第三方平台的函数会去执行授权流程。\n\n这种场景比如，使用谷歌的统一登录按钮：\n```示例\n\u003Cdiv id=\"g_id_onload\"\n     data-client_id=\"YOUR_GOOGLE_CLIENT_ID\"\n     data-login_uri=\"https://your.domain/your_login_endpoint\"\n     data-your_own_param_1_to_login=\"any_value\"\n     data-your_own_param_2_to_login=\"any_value\">\n\u003C/div>\n```\n![谷歌登录按钮](https://image.xinwei.ltd/11732766626888.png)\n\n具体解释可自行参考：[https://developers.google.com/identity/gsi/web/tools/configurator?hl=zh-cn](https://developers.google.com/identity/gsi/web/tools/configurator?hl=zh-cn)\n\n- 2. 引入第三方平台的库，使用javascript方法去调用第三方库的js api实现授权流程。\n\n还是拿Google登录举例：\n```示例\n\u003Cscript src=\"https://accounts.google.com/gsi/client\" async>\u003C/script>\n```\n```示例脚本\n\u003Cscript>\n  window.onload = function () {\n    google.accounts.id.initialize({\n      client_id: 'YOUR_GOOGLE_CLIENT_ID',\n      callback: handleCredentialResponse\n    });\n    google.accounts.id.prompt((notification) => {\n        if (notification.isNotDisplayed() || notification.isSkippedMoment()) {\n            // try next provider if OneTap is not displayed or skipped\n        }\n    });\n  }\n\u003C/script>\n```\n具体可自行查看：[https://developers.google.com/identity/gsi/web/guides/use-one-tap-js-api?hl=zh-cn](https://developers.google.com/identity/gsi/web/guides/use-one-tap-js-api?hl=zh-cn)\n\n- 3. 使用重定向的方式，在web当前域名网页下，跳转第三方平台的授权页，用户登录同意授权后，第三方平台的授权页会重定向回web的域名网页，重定向回来的web网页中会携带第三方平台加上的授权结果参数。前端开发同学，需要自己从网址参数读取参数值，并且将参数值通过接口请求发送给自己的后端同学进行处理。\n\n还是拿Google登录举例，通过重定向授权流程，最后重定向到的网址是：\n```示例授权返回后的网址\nhttps://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600\n```\n如上所示，会有一些参数拼接在前端同学的授权返回网址上。\n\n以上大概是目前在web前端开发中遇到的几种授权登录场景。\n\n> 提示：显示网页的设备环境是多种多样的，对于在网页授权过程中，如果是弹出式授权窗口(iframe)的形式，经常会被一些设备或者一些app的webkit给拦截掉，从而导致授权失败。\n\n\n## 二、前端web开发中实现Google登录的方法\n根据前言中所提到的一些信息，相信大家对授权登录的集成方式有了大概的理解，在这一节，我们聊一下如何优雅的实现一个不会被webkit拦截的授权登录方式。所以我选择重定向的方式。\n\nGoogle对OAuth 2.0 的授权流程是比较支持的，当然你也可以继续使用js API的方式，这里我选择OAuth 2.0的标准。\n\n1. 需要在Google console中创建项目，得到一个client id；\n2. 需要在Google console的项目中配置重定向网址；\n3. web前端中实现重定向的代码。\n\n官方有给出文档：[https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=zh-cn](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=zh-cn)\n\n### 2.1 实现Google登录必要的配置\n需要在Google的console控制台，新建一个项目，然后配置web的一些授权网域。\nGoogle的console网址：[https://console.cloud.google.com/](https://console.cloud.google.com/)\n\n![创建Google项目](https://image.xinwei.ltd/image1732767629549.png)\n\n在Google console中创建一个项目后，需要配置apis & services\n![apis & services](https://image.xinwei.ltd/image1732767724800.png)\n\n刚创建的项目是没有oauth client id的，所以需要创建一个新的：\n![OAuth client ID](https://image.xinwei.ltd/image1732767839169.png)\n\n创建完成后会得到client Id（我截图中有firebase自动创建的，这个在下一节会提到，大家可以忽略）\n![Client ID位置](https://image.xinwei.ltd/image1732768187572.png)\n\n配置授权回调网址，需要进入到OAuth consent Screen，然后点击进入编辑 \n![进入编辑](https://image.xinwei.ltd/image1732772143251.png)\n填写授权域名，这里可以填写重定向回调的网址域名，而且只能是顶级域名，当重定向成功后，会跳转到这个顶级域名下的网页（重定向的uri必须是这个顶级域名下的）\n![authorized domains](https://image.xinwei.ltd/image1732772314649.png)\n\n到这里，配置上的事情就结束了。\n\n### 2.2 发起重定向以及解析授权参数\n```google.ts\nfunction generateNonce() {\n  return [...Array(30)]\n    .map(() => Math.floor(Math.random() * 36).toString(36))\n    .join('');\n}\n// 实例化谷歌登录\nexport const googleSignAction = (callback: (success: boolean, msg?: string, res?: any) => void) => {\n            const clientId = \"xxx-xxx.apps.googleusercontent.com\";\n            const url = location.protocol.replaceAll(\":\", \"\") + \"://\" + location.host;\n\t    const redirectUri = encodeURIComponent(url);\n\t    const scope = encodeURIComponent(\"email profile openid\");\n\t    const responseType = \"id_token\"; // code、access_token\n      const nonce = generateNonce(); // id_token 必须要传此字段，且随机\n\t\t\n\t    const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=${responseType}&state=google&nonce=${nonce}`;\n\t    window.location.href = authUrl;\n}\n```\n> 提示：这里的ClientId，就是OAuth 2.0 Client IDs中的值\n\n![client id](https://image.xinwei.ltd/image1732772707047.png)\n\n接下来，当重定向成功后，解析我们自己网页中的参数：\n```google.ts\n/**\n * 当Google授权登录成功之后，读取path，从path中获取code，然后发送post请求，获取access token以及id token。\n * - id token用来和firebase进行授权，获取firebase的token。\n * - success：返回从firebase获取到的id token\n */\nexport const HandleGoogleAuthRedirect = async (path: string, success: (token: string) => void, fail: (err: any) => void) => {\n\tlet searchParams = path\n\tlet searchList = path.split(\"/#/\")\n\tif (searchList.length > 0) {\n\t\tsearchParams = searchList[searchList.length - 1]\n\t}\n\tsearchList = searchParams.split(\"?\")\n\tif (searchList.length > 0) {\n\t\tsearchParams = searchList[searchList.length - 1]\n\t}\n\tconst params = new URLSearchParams(searchParams)\n\tconst idToken = params.get('id_token')\n  ...\n}\n```\n这里的path参考前言一节中的重定向部分，网址携带参数的示例。\n\n这里的idToken就是Google的身份验证得到的标识，然后可以调用后端接口让服务端同学去处理了。\n\n## 三、前端web开发中实现firebase的Google登录的方法\n众所周知，firebase是Google的一个大一统集成平台，firebase上可以集成Crashly、firestore、login等一系列的功能，使用firebase可以很清晰的查看各种统计数据，各个功能的数据可以同步进行对比分析。\n\n所以firebase自然提供了Google登录，当然他还支持Facebook、Apple、Twitter登录等平台。\n\nfirebase的web登录同样是会得到一个id token，但是这个id token和Google得到的id token不是同一个字符串，是只针对firebase平台的。\n\n使用firebase登录需要在firebase的console平台上创建项目并且进行配置。\nfirebase console的链接是：[https://console.firebase.google.com/](https://console.firebase.google.com/)\n\n### 3.1 创建firebase项目并且配置\n进入控制台，创建一个新项目\n![创建新项目](https://image.xinwei.ltd/image1732773565975.png)\n\n创建完项目后，给这个项目添加web平台\n![添加应用](https://image.xinwei.ltd/image1732773746221.png)\n\n选择添加web平台\n![firebase web](https://image.xinwei.ltd/image1732773709899.png)\n\n根据以下截图，按照序号顺序，选择使用google作为身份提供方\n![firebase google](https://image.xinwei.ltd/image1732773858431.png)\n\n添加完后，进行编辑，一定要选择启用Google\n![firebase google](https://image.xinwei.ltd/image1732773945666.png)\n\n添加授权网域，需要把我们的网页域名添加到这里的“已获授权的网域”\n![auth domains](https://image.xinwei.ltd/image1732774056910.png)\n\n至此，前端相关的配置到这里就结束了，另外提一句，后端需要的文件也在这个控制台里，以下是截图：\n![server side usage](https://image.xinwei.ltd/image1732774393582.png)\n\n### 3.2 代码使用firebase\n查看前端的firebase集成代码，还是在刚刚的“项目设置”页面下，在“常规”这一项下，滑动到最底部，查看web的集成代码\n![project setting](https://image.xinwei.ltd/image1732774591497.png)\n\n滚动到最底部的地方，就有集成方式介绍\n![npm firebase](https://image.xinwei.ltd/image1732774714790.png)\n\n集成后查看package.json文件：\n```package.json\n{\n  \"dependencies\": {\n    ...\n    \"firebase\": \"^10.12.3\",\n    ...\n  },\n  ...\n}\n```\n\n我先把firebase的示例代码贴到这里：\n```firebase.ts\n// Import the functions you need from the SDKs you need\nimport { initializeApp } from \"firebase/app\";\nimport { getAnalytics } from \"firebase/analytics\";\n// TODO: Add SDKs for Firebase products that you want to use\n// https://firebase.google.com/docs/web/setup#available-libraries\n\n// Your web app's Firebase configuration\n// For Firebase JS SDK v7.20.0 and later, measurementId is optional\nconst firebaseConfig = {\n  apiKey: \"AxxxxQk\",\n  authDomain: \"xxx.firebaseapp.com\",\n  projectId: \"xxxx\",\n  storageBucket: \"xxx.firebasestorage.app\",\n  messagingSenderId: \"xxxx\",\n  appId: \"1:xxxx:web:xxxx\",\n  measurementId: \"G-xxx\"\n};\n\n// Initialize Firebase\nconst app = initializeApp(firebaseConfig);\nconst analytics = getAnalytics(app);\n```\n\n我们这里由于使用到了Google的身份验证，所以其实需要引入firebase的Google提供API，所以我贴一下我自己的代码：（`GetCurrentFirebase`表示的就是上面示例中的`firebaseConfig`）\n```示例.ts\nimport { initializeApp } from \"firebase/app\";\nimport { getAuth, signInWithPopup, GoogleAuthProvider } from \"firebase/auth\";\nimport { GetCurrentFirebase } from \"../../../env/firebase\";\n\nconst firebaseConfig = GetCurrentFirebase()\n\nconst app = initializeApp(firebaseConfig);\nconst auth = getAuth(app);\nconst provider = new GoogleAuthProvider();\n\nexport { auth, provider, signInWithPopup, GoogleAuthProvider };\n```\n\n然后我们在点击按钮后，要唤起授权弹窗：\n```示例.ts\n...\nimport { auth, provider, signInWithPopup, GoogleAuthProvider  } from \"./firebase\";\n...\n\nconst onClickGoogleBtn = (callback: (success: boolean, msg?: string, res?: any) => void) => {\n  signInWithPopup(auth, provider)\n\t  .then(async (result) => {\n\t\t  console.log(\"Google授权登录获取到的值是：\", res);\n\t\t  if (result && result.user) {\n\t\t\t  const user = result.user;\n\t\t\t  user.getIdToken(true).then((id_token) => {\n                              callback(true, id_token, res)\n\t\t\t  }).catch(error => {\n                              callback(false, typeof error == 'string' ? error : (error.message || error.msg));\n\t\t\t  });\n\t\t  } else {\n\t\t\t  callback(false, TXT_PleaseTryAgain);\n\t\t  }\n\t  })\n\t  .catch((error) => {\n\t\tconsole.log(\"Google授权登录失败是：\", error);\n\t\tcallback(false, typeof error == 'string' ? error : (error.message || error.msg));\n\t  });\n}\n```\n\n这样就能获取到firebase的id token了，但是这种方式就是通过弹窗授权的方式，而弹窗授权在一些设备的浏览器或者某些app的webview中显示时，弹窗会被禁止，从而导致弹窗授权失败。\n\n所以我们必须得使用一个不使用弹窗的授权方式，保证授权登录能成功。\n\n我看了firebase的很多文档，他们虽然提供了fireabse的重定向登录方式，但是对于fireabse的验证有诸多限制，是不容易实现的。\n\nfirebase的官方集成文档的几种方式：[https://firebase.google.com/docs/auth/web/redirect-best-practices?hl=zh-cn](https://firebase.google.com/docs/auth/web/redirect-best-practices?hl=zh-cn)\n\n总结来说，firebase的重定向方式有以下几种：\n![firebase授权示例](https://image.xinwei.ltd/image1732776357150.png)\n\n其实就一句话，那就是如果只使用firebase的方式进行Google授权，而且使用重定向的方式，那么服务端要么使用firebase Hosting，要么自行托管firebase授权文件。\n\n所以仅仅靠firebase实现Google的授权重定向登录，是不够的，下一节我们会说到。\n\n## 四、结合Google授权登录、firebase身份转换达到获取firebase的id token\n从这一节的标题看，相信大家已经知道如何去优雅的实现一个firebase的Google重定向登录。而且只需要使用第二、三种的代码，稍加改进就可以实现。\n\n总的流程就是：\n1. 使用Google的重定向获取Google的id token；\n2. 使用firebase的身份credential将Google的id token转成firebase的id token。\n\n第一步，通过第二节的代码，就可以得到，所以这里不再赘述。\n\n第二步，将firebase的授权登录代码，改造一下：\n```firebase.ts\n...\nimport { signInWithCredential } from \"firebase/auth\";\nimport { auth, GoogleAuthProvider  } from \"./firebase\";\n...\n\n        const params = new URLSearchParams(searchParams)\n\tconst idToken = params.get('id_token')\n\tif (idToken != null && idToken.length > 0) {\n\t\tconst credential = GoogleAuthProvider.credential(idToken);\n\t\tconst result = await signInWithCredential(auth, credential);\n\t\tconst user = result.user;\n\t\tuser.getIdToken(true).then((token) => {\n\t\t\t// 这里获取到的就是firebase 的 id token\n\t\t\tsuccess(token);\n\t\t}).catch(error => {\n\t\t\t// 这里就是出错了\n\t\t\tfail(error)\n\t\t});\n\t}\n...\n```\n上面的这个代码，大家应该能看懂吧，`auth`和`GoogleAuthProvider`，是在第三章的代码中`export`的，这里从`firebase`库中引入了`signInWithCredential`了。\n\n\n\n全篇就写到这里，希望对大家有帮助。\n\n\n\n\n\n\n\n\n\n","今天简单记录一下如何在web前端开发中实现Google登录和firebase登录，重点在如何使用谷歌登录后，转成firebase的登录方法。","https://image.xinwei.ltd/11732766626888.png",499668042977349,{"phone":15,"userId":13,"nickName":16,"vipType":17,"avatar":18,"sign":19,"createdAt":20},"13121171998","全栈老韩",1,"https://image.xinwei.ltd/images/IMG_5430.JPG","全栈工程师，擅长iOS App开发、前端（vue、react、nuxt、小程序&Taro）开发、Flutter、React Native、后端（midwayjs、golang、express、koa）开发、docker容器、seo优化等。","2024-01-01T16:14:30.305Z",2,{"id":21,"name":23},"IT技术",32,{"id":24,"name":26,"parentId":21},"前端",[],"",1918,0,"google login,firebase google login,firebase login,firebase google,firebase,google,google login token,web firebase,web google,web谷歌登录,web firebase登录,谷歌登录,firebase登录"]