程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2022-02(5)

2022-03(73)

2022-04(9)

2022-05(14)

2022-06(1)

OAuth 2.0 (第三方登录)前端流程实现

发布于2022-11-19 06:31     阅读(515)     评论(0)     点赞(1)     收藏(3)


目录

一、OAuth是什么

二、OAuth 实现,前端需要做什么

(一)数据收集表单

(二)获取后端返回值

(三)重定向地址及后续处理

三、项目地址


一、OAuth是什么

        OAuth就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。也就是大家口中熟知的第三方登录,通过微信号或qq号授权去登录各类app或网站。

        因为博主目前是一名菜鸡前端,所以对OAuth的后端实现并不是很了解,所以,本篇文章着重讲OAuth的前端实现思路。

        想了解后端运作机制的请点击下面这个大佬写的文章。写的还是非常详细的。虽然我看不太懂。理解OAuth 2.0 - 阮一峰的网络日志https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

二、OAuth 实现,前端需要做什么

(一)数据收集表单

        大家对于这个表单可能会有一些不了解的地方,下面我来一一解释一下。

字段解释
OAuth类型OAuth类型你可以理解为个个应用对于请求地址的参数上传是不一致的,你需要对个个应用的地址做一些相对应的处理,而OAuth类型就可以作为对这些应用的区分的关键信息。
名称顾名思义,就是名称
客户端idClient ID 由第三方软件生成的,唯一的值。
客户端密钥Client Secret 由第三方软件生成的,唯一的值。
回调地址回调地址是用于 OAuth认证完回跳时的访问地址,默认填充为当前访问地址。通常也需要您在Oauth 服务提供商进行相同的配置
自动登录这个是平台实现的功能,就是当你已经登录了自己的平台账号,再去点击第三方登录,就跳过第三方的登录页面,直接进入第三方应用。这里是本产品特有的功能。大家可以忽略不计。

        这里我们通过表单把数据返回给后端,后端再去做一些处理,拼接地址,加密等等。

  1. handleCreatOauth = values => {
  2. let {
  3. name,
  4. client_id,
  5. client_secret,
  6. oauth_type,
  7. home_url,
  8. redirect_domain,
  9. is_auto_login
  10. } = values;
  11. oauth_type = oauth_type.toLowerCase();
  12. if (oauth_type === 'github') {
  13. home_url = 'https://github.com';
  14. }
  15. if (oauth_type === 'aliyun') {
  16. home_url = 'https://oauth.aliyun.com';
  17. }
  18. if (oauth_type === 'dingtalk') {
  19. home_url = 'https://oapi.dingtalk.com';
  20. }
  21. const obj = {
  22. name,
  23. client_id,
  24. client_secret,
  25. is_auto_login,
  26. oauth_type,
  27. redirect_uri: `${redirect_domain}/console/oauth/redirect`,
  28. home_url,
  29. is_console: true
  30. };
  31. this.handelRequest(obj);
  32. };
  33. handelRequest = (obj = {}, isclone) => {
  34. const { dispatch, eid } = this.props;
  35. const { oauthInfo, oauthTable, isOpen } = this.state;
  36. const arr = [...oauthTable];
  37. obj.eid = eid;
  38. oauthInfo
  39. ? (obj.service_id = oauthInfo.service_id)
  40. : (obj.service_id = null);
  41. isclone ? (obj.enable = false) : (obj.enable = true);
  42. if (oauthTable && oauthTable.length > 0) {
  43. oauthTable.map((item, index) => {
  44. const { service_id } = item;
  45. arr[index].is_console = true;
  46. if (oauthInfo && service_id === obj.service_id) {
  47. arr[index] = Object.assign(arr[index], obj);
  48. }
  49. });
  50. }
  51. !oauthInfo && arr.push(obj);
  52. dispatch({
  53. type: 'global/creatOauth',
  54. payload: {
  55. enterprise_id: eid,
  56. arr
  57. },
  58. callback: data => {
  59. if (data && data.status_code === 200) {
  60. notification.success({
  61. message: isOpen
  62. ? formatMessage({id:'notification.success.open'})
  63. : isclone
  64. ? formatMessage({id:'notification.success.close'})
  65. : oauthInfo
  66. ? formatMessage({id:'notification.success.edit'})
  67. : formatMessage({id:'notification.success.add'})
  68. });
  69. this.handelOauthInfo();
  70. }
  71. }
  72. });
  73. };

        以上代码是本项目提交事件的触发函数。 可以看到,oauth_type就作为识别各类应用\网站的关键信息。通过识别从而返回不同的home_url,发送给后端。

(二)获取后端返回值

        当我们把完整的信息提交给后端以后,在登录页面,我们就要对接对应的第三方登录入口。

        如上图,进入页面以后,先调了一个接口,这里返回了一个关键的信息,就是“authorize_url”。这里我们先暂时不做解释,接着往下看。

  1. {oauthServicesList.map(item => {
  2. const { name, service_id } = item;
  3. return (
  4. <div className={styles.thirdCol} key={service_id}>
  5. <Tooltip placement="top" title={name}>
  6. <a
  7. style={inlineBlock}
  8. href={oauthUtil.getAuthredictURL(item)}
  9. title={name}
  10. >
  11. {oauthUtil.getIcon(item)}
  12. </a>
  13. </Tooltip>
  14. </div>
  15. );
  16. })}

        这里是html部分,oauthServicesList就是获取的上面那个接口返回的对接好的第三方应用,通过map循环渲染出图标,然后a链接点接以后拿到对应的“item”传进该方法然后return出返回值进行跳转。

  1. getAuthredictURL(item) {
  2. if (item) {
  3. const {
  4. oauth_type: oauthType,
  5. client_id: clientId,
  6. auth_url: authUrl,
  7. redirect_uri: redirectUri,
  8. service_id: serviceId,
  9. authorize_url: authorizeUrl
  10. } = item;
  11. if (oauthType === 'enterprisecenter' && authorizeUrl) {
  12. const str = authorizeUrl;
  13. const agreement = `${window.location.protocol}//`;
  14. const content = window.location.host;
  15. const suffix = str.substring(
  16. str.indexOf('/enterprise-server'),
  17. str.length
  18. );
  19. const newUrl = agreement + content + suffix;
  20. const isRedirectUrl = newUrl.indexOf('redirect_uri=') > -1;
  21. const redirectbefore =
  22. isRedirectUrl && newUrl.substring(0, newUrl.indexOf('redirect_uri='));
  23. const redirectSuffix =
  24. isRedirectUrl &&
  25. newUrl.substring(newUrl.indexOf('/console'), newUrl.length);
  26. const url = isRedirectUrl
  27. ? `${`${redirectbefore}redirect_uri=${agreement}${content}`}${redirectSuffix}`
  28. : newUrl;
  29. return url;
  30. }
  31. if (authorizeUrl) {
  32. return authorizeUrl;
  33. }
  34. if (oauthType == 'github') {
  35. return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&scope=user%20repo%20admin:repo_hook`;
  36. }
  37. return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&response_type=code`;
  38. }
  39. return null;
  40. },

        以上就是 getAuthredictURL方法。可以看到oauth_type仍然作为区分各种类型的关键信息。从而返回不同的url。其中一种判定方法就是当authorizUrl不为空时,返回authorizUrl。这里的authorizUrl就是我上文提到的,后端返回的关键信息'authorize_url'

https://rainhome.goodrain.com/oauth/authorize?client_id=48948d5082eacd0dd4d9&scope=snsapi_login&redirect_uri=http%3A//localhost%3A8080/console/oauth/redirect%3Fservice_id%3D325&response_type=code"

        可以看到这条url拼接了很多信息,有client_id,redirect_url等等。并且做了相应的加密处理 。

        当我们完成这一步时,前端的工作就已经完成了一半了。

(三)重定向地址及后续处理

        还记得在填写表单时有一个重定向地址吗,'redirect_url',在你点击a链接跳转以后,后端进行一系列操作,然后就会调这个重定向的地址,并在地址栏上返回相应的 code码 和 service_id码。然后再调一个认证接口。

        当然事前你要写一个对应的路由地址。     

  1. /* eslint-disable no-underscore-dangle */
  2. /* eslint-disable camelcase */
  3. import { message } from 'antd';
  4. import { connect } from 'dva';
  5. import { routerRedux } from 'dva/router';
  6. import React, { Component } from 'react';
  7. import { formatMessage, FormattedMessage } from 'umi-plugin-locale';
  8. import Result from '../../components/Result';
  9. import cookie from '../../utils/cookie';
  10. import handleAPIError from '../../utils/error';
  11. import globalUtil from '../../utils/global';
  12. import rainbondUtil from '../../utils/rainbond';
  13. const loginUrl = '/user/login?disable_auto_login=true';
  14. @connect()
  15. export default class ThirdLogin extends Component {
  16. constructor(props) {
  17. super(props);
  18. this.state = {
  19. resultState: 'ing',
  20. title: formatMessage({id:'login.Third.authentication'}),
  21. desc: formatMessage({id:'login.Third.wait_for'})
  22. };
  23. }
  24. // eslint-disable-next-line consistent-return
  25. componentWillMount() {
  26. const code = rainbondUtil.OauthParameter('code');
  27. const service_id = rainbondUtil.OauthParameter('service_id');
  28. const { dispatch } = this.props;
  29. if (
  30. code &&
  31. service_id &&
  32. code !== 'None' &&
  33. service_id !== 'None' &&
  34. code !== '' &&
  35. service_id !== ''
  36. ) {
  37. const token = cookie.get('token');
  38. // if user login
  39. if (token) {
  40. dispatch({ type: 'global/hideNeedLogin' });
  41. dispatch({
  42. type: 'user/fetchThirdLoginBinding',
  43. payload: {
  44. code,
  45. service_id
  46. },
  47. callback: res => {
  48. if (res) {
  49. const status = res.response_data && res.response_data.status;
  50. if (status && status === 400) {
  51. this.setState(
  52. {
  53. resultState: 'error',
  54. title: formatMessage({id:'login.Third.Failed'}),
  55. desc: formatMessage({id:'login.Third.Authentication'})
  56. },
  57. () => {
  58. setTimeout(() => {
  59. this.handleLoginUrl();
  60. }, 1000);
  61. }
  62. );
  63. } else if (res.status_code && res.status_code === 200) {
  64. this.setState(
  65. {
  66. resultState: 'success',
  67. title: formatMessage({id:'login.Third.success'}),
  68. desc: ''
  69. },
  70. () => {
  71. if (res.bean && res.bean.token) {
  72. cookie.set('token', res.bean.token);
  73. }
  74. this.handleSuccess();
  75. }
  76. );
  77. } else {
  78. this.handleLoginUrl();
  79. }
  80. } else {
  81. this.handleLoginUrl();
  82. }
  83. },
  84. handleError: err => {
  85. this.handleError(err);
  86. }
  87. });
  88. return null;
  89. }
  90. globalUtil.removeCookie();
  91. // if not login
  92. dispatch({
  93. type: 'user/fetchThirdCertification',
  94. payload: {
  95. code,
  96. service_id,
  97. domain: window.location.host
  98. },
  99. callback: res => {
  100. if (res) {
  101. const status = res.response_data && res.response_data.status;
  102. if (
  103. status &&
  104. (status === 400 || status === 401 || status === 404)
  105. ) {
  106. this.setState(
  107. {
  108. resultState: 'error',
  109. title: formatMessage({id:'login.Third.Failed'}),
  110. desc: res.msg_show || formatMessage({id:'login.Third.token'})
  111. },
  112. () => {
  113. setTimeout(() => {
  114. this.handleLoginUrl();
  115. }, 1000);
  116. }
  117. );
  118. } else if (res.status_code === 200) {
  119. const data = res.bean;
  120. if (data && data.token) {
  121. cookie.set('token', data.token);
  122. this.handleSuccess();
  123. return null;
  124. }
  125. if (data && data.result) {
  126. // if not login
  127. if (!data.result.is_authenticated) {
  128. dispatch(
  129. routerRedux.push(
  130. `/user/third/register?code=${data.result.code}&service_id=${data.result.service_id}&oauth_user_id=${data.result.oauth_user_id}&oauth_type=${data.result.oauth_type}`
  131. )
  132. );
  133. } else {
  134. dispatch(
  135. routerRedux.push(
  136. `/user/third/login?code=${data.result.code}&service_id=${data.result.service_id}&oauth_user_id=${data.result.oauth_user_id}&oauth_type=${data.result.oauth_type}`
  137. )
  138. );
  139. }
  140. }
  141. } else {
  142. this.handleLoginUrl();
  143. }
  144. } else {
  145. this.handleLoginUrl();
  146. }
  147. },
  148. handleError: err => {
  149. this.handleError(err);
  150. }
  151. });
  152. } else {
  153. globalUtil.removeCookie();
  154. dispatch(routerRedux.replace(loginUrl));
  155. }
  156. }
  157. handleLoginUrl = () => {
  158. const { dispatch } = this.props;
  159. dispatch(routerRedux.push(loginUrl));
  160. };
  161. handleError = err => {
  162. const status = (err && err.status) || (err.response && err.response.status);
  163. if (status && status === 500) {
  164. message.warning(formatMessage({id:'login.Third.again'}));
  165. } else {
  166. handleAPIError(err);
  167. }
  168. setTimeout(() => {
  169. this.handleLoginUrl();
  170. }, 1000);
  171. };
  172. handleSuccess = () => {
  173. const { dispatch } = this.props;
  174. let redirect = window.localStorage.getItem('redirect');
  175. if (!redirect || redirect === '') {
  176. redirect = '/';
  177. }
  178. window.localStorage.setItem('redirect', '');
  179. if (redirect.startsWith('/')) {
  180. dispatch(routerRedux.push(redirect));
  181. } else {
  182. window.location.href = redirect;
  183. }
  184. };
  185. render() {
  186. const { resultState, title, desc } = this.state;
  187. return (
  188. <Result
  189. type={resultState}
  190. title={title}
  191. description={desc}
  192. style={{
  193. marginTop: '20%',
  194. marginBottom: 16
  195. }}
  196. />
  197. );
  198. }
  199. }

        这里的判断有些多,先判断有没有登录再判断认证成功不成功,如果登录了并认证成功则将返回的token值设置到cookie的token里然后直接进入应用,登录了但失败则返回认证失败。如果没登录则直接跳转到第三方应用的登录页面。如果都登陆了,但登录的账号没有与本平台关联的账号,则跳转到本平台的登录/注册页面。当你再次登陆时就会将登陆的账号与第三方账号进行关联了。

        这样就形成了一个闭环。也就完成了OAuth认证前端该做的所有事情。

        以下为实现效果。

三、项目地址

主项目地址:

GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | 云原生多云应用管理平台Cloud native multi cloud application management platform | 云原生多云应用管理平台 - GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | 云原生多云应用管理平台https://github.com/goodrain/rainbond

前端项目地址 :

GitHub - goodrain/rainbond-ui: Rainbond front-end projectRainbond front-end project . Contribute to goodrain/rainbond-ui development by creating an account on GitHub.https://github.com/goodrain/rainbond-uiOAuth表单地址:

        src > components > OauthForm > index.js

登录页面地址:

        src > pages > user > login.js 

重定向页面地址:

        src > pages > user > Third.js 

获取a链接url工具地址:

        src > utils > oauth.js

代码已经开源,欢迎fork与start!

原文链接:https://blog.csdn.net/qq_45799465/article/details/127634189




所属网站分类: 技术文章 > 博客

作者:天使的翅膀

链接:http://www.qianduanheidong.com/blog/article/455448/755d525f18cb9a1675e4/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

1 0
收藏该文
已收藏

评论内容:(最多支持255个字符)