小言_互联网的博客

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

582人阅读  评论(0)

目录

一、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类型就可以作为对这些应用的区分的关键信息。
名称 顾名思义,就是名称
客户端id Client 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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场