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

news2025/7/9 17:24:28

目录

一、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 服务提供商进行相同的配置
自动登录这个是平台实现的功能,就是当你已经登录了自己的平台账号,再去点击第三方登录,就跳过第三方的登录页面,直接进入第三方应用。这里是本产品特有的功能。大家可以忽略不计。

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

  handleCreatOauth = values => {
    let {
      name,
      client_id,
      client_secret,
      oauth_type,
      home_url,
      redirect_domain,
      is_auto_login
    } = values;
    oauth_type = oauth_type.toLowerCase();
    if (oauth_type === 'github') {
      home_url = 'https://github.com';
    }
    if (oauth_type === 'aliyun') {
      home_url = 'https://oauth.aliyun.com';
    }
    if (oauth_type === 'dingtalk') {
      home_url = 'https://oapi.dingtalk.com';
    }
    const obj = {
      name,
      client_id,
      client_secret,
      is_auto_login,
      oauth_type,
      redirect_uri: `${redirect_domain}/console/oauth/redirect`,
      home_url,
      is_console: true
    };
    this.handelRequest(obj);
  };
  handelRequest = (obj = {}, isclone) => {
    const { dispatch, eid } = this.props;
    const { oauthInfo, oauthTable, isOpen } = this.state;
    const arr = [...oauthTable];
    obj.eid = eid;
    oauthInfo
      ? (obj.service_id = oauthInfo.service_id)
      : (obj.service_id = null);
    isclone ? (obj.enable = false) : (obj.enable = true);

    if (oauthTable && oauthTable.length > 0) {
      oauthTable.map((item, index) => {
        const { service_id } = item;
        arr[index].is_console = true;
        if (oauthInfo && service_id === obj.service_id) {
          arr[index] = Object.assign(arr[index], obj);
        }
      });
    }
    !oauthInfo && arr.push(obj);
    dispatch({
      type: 'global/creatOauth',
      payload: {
        enterprise_id: eid,
        arr
      },
      callback: data => {
        if (data && data.status_code === 200) {
          notification.success({
            message: isOpen
              ? formatMessage({id:'notification.success.open'})
              : isclone
              ? formatMessage({id:'notification.success.close'})
              : oauthInfo
              ? formatMessage({id:'notification.success.edit'})
              : formatMessage({id:'notification.success.add'})
          });
          this.handelOauthInfo();
        }
      }
    });
  };

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

(二)获取后端返回值

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

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

{oauthServicesList.map(item => {
  const { name, service_id } = item;
  return (
    <div className={styles.thirdCol} key={service_id}>
      <Tooltip placement="top" title={name}>
        <a
          style={inlineBlock}
          href={oauthUtil.getAuthredictURL(item)}
          title={name}
        >
          {oauthUtil.getIcon(item)}
        </a>
      </Tooltip>
    </div>
  );
})}

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

  getAuthredictURL(item) {
    if (item) {
      const {
        oauth_type: oauthType,
        client_id: clientId,
        auth_url: authUrl,
        redirect_uri: redirectUri,
        service_id: serviceId,
        authorize_url: authorizeUrl
      } = item;
      if (oauthType === 'enterprisecenter' && authorizeUrl) {
        const str = authorizeUrl;
        const agreement = `${window.location.protocol}//`;
        const content = window.location.host;
        const suffix = str.substring(
          str.indexOf('/enterprise-server'),
          str.length
        );
        const newUrl = agreement + content + suffix;
        const isRedirectUrl = newUrl.indexOf('redirect_uri=') > -1;
        const redirectbefore =
          isRedirectUrl && newUrl.substring(0, newUrl.indexOf('redirect_uri='));

        const redirectSuffix =
          isRedirectUrl &&
          newUrl.substring(newUrl.indexOf('/console'), newUrl.length);
        const url = isRedirectUrl
          ? `${`${redirectbefore}redirect_uri=${agreement}${content}`}${redirectSuffix}`
          : newUrl;
        return url;
      }

      if (authorizeUrl) {
        return authorizeUrl;
      }
      if (oauthType == 'github') {
        return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&scope=user%20repo%20admin:repo_hook`;
      }
      return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&response_type=code`;
    }
    return null;
  },

        以上就是 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码。然后再调一个认证接口。

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

/* eslint-disable no-underscore-dangle */
/* eslint-disable camelcase */
import { message } from 'antd';
import { connect } from 'dva';
import { routerRedux } from 'dva/router';
import React, { Component } from 'react';
import { formatMessage, FormattedMessage } from 'umi-plugin-locale';
import Result from '../../components/Result';
import cookie from '../../utils/cookie';
import handleAPIError from '../../utils/error';
import globalUtil from '../../utils/global';
import rainbondUtil from '../../utils/rainbond';

const loginUrl = '/user/login?disable_auto_login=true';

@connect()
export default class ThirdLogin extends Component {
  constructor(props) {
    super(props);
    this.state = {
      resultState: 'ing',
      title: formatMessage({id:'login.Third.authentication'}),
      desc: formatMessage({id:'login.Third.wait_for'})
    };
  }
  // eslint-disable-next-line consistent-return
  componentWillMount() {
    const code = rainbondUtil.OauthParameter('code');
    const service_id = rainbondUtil.OauthParameter('service_id');
    const { dispatch } = this.props;
    if (
      code &&
      service_id &&
      code !== 'None' &&
      service_id !== 'None' &&
      code !== '' &&
      service_id !== ''
    ) {
      const token = cookie.get('token');
      // if user login
      if (token) {
        dispatch({ type: 'global/hideNeedLogin' });
        dispatch({
          type: 'user/fetchThirdLoginBinding',
          payload: {
            code,
            service_id
          },
          callback: res => {
            if (res) {
              const status = res.response_data && res.response_data.status;
              if (status && status === 400) {
                this.setState(
                  {
                    resultState: 'error',
                    title: formatMessage({id:'login.Third.Failed'}),
                    desc: formatMessage({id:'login.Third.Authentication'})
                  },
                  () => {
                    setTimeout(() => {
                      this.handleLoginUrl();
                    }, 1000);
                  }
                );
              } else if (res.status_code && res.status_code === 200) {
                this.setState(
                  {
                    resultState: 'success',
                    title: formatMessage({id:'login.Third.success'}),
                    desc: ''
                  },
                  () => {
                    if (res.bean && res.bean.token) {
                      cookie.set('token', res.bean.token);
                    }
                    this.handleSuccess();
                  }
                );
              } else {
                this.handleLoginUrl();
              }
            } else {
              this.handleLoginUrl();
            }
          },
          handleError: err => {
            this.handleError(err);
          }
        });
        return null;
      }
      globalUtil.removeCookie();
      // if not login
      dispatch({
        type: 'user/fetchThirdCertification',
        payload: {
          code,
          service_id,
          domain: window.location.host
        },
        callback: res => {
          if (res) {
            const status = res.response_data && res.response_data.status;
            if (
              status &&
              (status === 400 || status === 401 || status === 404)
            ) {
              this.setState(
                {
                  resultState: 'error',
                  title: formatMessage({id:'login.Third.Failed'}),
                  desc: res.msg_show || formatMessage({id:'login.Third.token'})
                },
                () => {
                  setTimeout(() => {
                    this.handleLoginUrl();
                  }, 1000);
                }
              );
            } else if (res.status_code === 200) {
              const data = res.bean;
              if (data && data.token) {
                cookie.set('token', data.token);
                this.handleSuccess();
                return null;
              }
              if (data && data.result) {
                // if not login
                if (!data.result.is_authenticated) {
                  dispatch(
                    routerRedux.push(
                      `/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}`
                    )
                  );
                } else {
                  dispatch(
                    routerRedux.push(
                      `/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}`
                    )
                  );
                }
              }
            } else {
              this.handleLoginUrl();
            }
          } else {
            this.handleLoginUrl();
          }
        },
        handleError: err => {
          this.handleError(err);
        }
      });
    } else {
      globalUtil.removeCookie();
      dispatch(routerRedux.replace(loginUrl));
    }
  }
  handleLoginUrl = () => {
    const { dispatch } = this.props;
    dispatch(routerRedux.push(loginUrl));
  };
  handleError = err => {
    const status = (err && err.status) || (err.response && err.response.status);
    if (status && status === 500) {
      message.warning(formatMessage({id:'login.Third.again'}));
    } else {
      handleAPIError(err);
    }
    setTimeout(() => {
      this.handleLoginUrl();
    }, 1000);
  };
  handleSuccess = () => {
    const { dispatch } = this.props;
    let redirect = window.localStorage.getItem('redirect');
    if (!redirect || redirect === '') {
      redirect = '/';
    }
    window.localStorage.setItem('redirect', '');
    if (redirect.startsWith('/')) {
      dispatch(routerRedux.push(redirect));
    } else {
      window.location.href = redirect;
    }
  };

  render() {
    const { resultState, title, desc } = this.state;
    return (
      <Result
        type={resultState}
        title={title}
        description={desc}
        style={{
          marginTop: '20%',
          marginBottom: 16
        }}
      />
    );
  }
}

        这里的判断有些多,先判断有没有登录再判断认证成功不成功,如果登录了并认证成功则将返回的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!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue-router入门学习3

命名路由 使用命名路由实现声明式导航 要在路由规则里加上name属性&#xff0c;相对于给这个地址取别名&#xff0c;当地址非常长时可以充分体现它的好用之处。 这里的to属性前面要加个冒号噢 调用push函数期间指定一个配置对象&#xff0c;name是要跳转到的路由规则、params是…

真题集P127---2018年真题

真题集P127---2018年真题第一题思路辗转相除法代码第三题思路代码第六题思路代码第一题 思路 就是在考学生&#xff0c;如何快速求解最大公约数问题 <1>从mn中选较小的数开始向下枚举&#xff0c;一直到2为止&#xff0c;第一个能同时整除m,n的即为所求(暴力枚举不推荐)…

Java这些最基础的知识,你还记得多少?

目录 【一】前言 【二】基础数据类型 【三】修饰符 【四】集合 【五】总结 【一】前言 作为一名CS本专业的程序员来说&#xff0c;大学学过的计算机课程&#xff0c;如&#xff1a;编程语言基本语法、数据结构、算法、计算机网络、计算机组成原理、数据库、汇编都是计算机…

nodejs基于微信小程序的书籍销售系统--(ssm+uinapp+Mysql)

伴随着社会以及科学技术的发展,互联网已经渗透在人们的身边,网络慢慢的变成了人们的生活必不可少的一部分,随着互联网的飞速发展,系统这一名词已不陌生,越来越多的书店都会使用系统来定制一款属于自己个性化的系统。书籍销售系统采用nodejs技术, mysql数据库进行开发,实现了首页…

人脑能否重启?

1.重启是什么 “人脑能否重启”这个问题还不简单&#xff0c;人睡眠后清醒就是重启。 事实真的是如此简单吗&#xff1f;我们先不急着给出结论&#xff0c;前面提到“人睡眠后清醒就是重启”&#xff0c;这句话中有两概念&#xff1a; 1、睡眠和觉醒&#xff0c;这是两种人脑…

计算机算法与分析--算法实现题1

代码网上一抓一大把&#xff0c;所以我就不贴代码了&#xff0c;主要讲讲解题的思路&#xff0c;相信各位都能随便写出来。 1-1 统计数字问题 第一反应就是直接循环&#xff0c;然后每个数字进行统计。虽然很容易想到&#xff0c;但肯定会超时的宝贝&#xff01;用心找到位数…

MySQL梳理

MySQL数据库总结篇_许小许520的博客-CSDN博客_mysql数据库文章 MySQL简介 MySQL安装 MySQL建库/表/记录 MySQL基本操作语句 MySQL字段类型&#xff0c;约束条件 int,float...pk,unique.. MySQL单/多表关系 四个关系 MySQL单/多表查询 MySQL查询关键字 where&#xff0c;havi…

学编程:Python入门考级必备[5]

海龟画图(5) 妙手丹青 一、初始化 二、坐标与角度 三、画圆与点与技巧 四、填充颜色与圆内内切多边形 炼 海龟画图 一、初始化 英文:turtle 中文:海龟 海龟能在一个画布上游走&#xff0c;游走的轨迹就形成了绘制的图形。 1.怎么在 Python 编程中找到海龟呢&#xff1f; …

从0到1实现python基于RPC协议的接口自动化测试

01、什么是RPC RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用协议是一个用于建立适当框架的协议。从本质上讲&#xff0c;它使一台机器上的程序能够调用另一台机器上的子程序&#xff0c;而不会意识到它是远程的。 RPC 是一种软件通信协议&#xff0c;一个程…

安装HBase集群

安装HBase集群 1 软件环境 注&#xff1a;我的Hadoop环境已经安装完成。 1.1 版本选择 版本的兼容问题很重要&#xff01;&#xff01; 软件环境版本号备注CentOS77.6JDK1.8.0Hadoop3.1.3hadoop-3.1.3.tar.gzZooKeeper3.6.3apache-zookeeper-3.6.3-bin.tar.gzHBase2.4.15h…

宝塔centos7安装Conda

前言&#xff1a;最近学习了python&#xff0c;主要原因是公司主营百度相关业务&#xff0c;接触了一下paddle Ai开发套件&#xff0c;其中paddlehub安装一直有问题&#xff0c;windows环境也好还是liunx环境也好一直安装不了最新版本&#xff0c;应该是某个库版本问题&#xf…

shell脚本编程

shell介绍 Shell的本意是“壳”的意思,它是相对于操作系统的“壳”。外界的操作不能直接调用操作系统内核,需要通过Shell脚本进行调用。 Shell本质是一个解释器程序,用于接受应用程序/用户命令,然后调用操作系统内核。Shell还是一个功能相当强大的编程语言,易编写,易调…

c++的STL+string

目录 STL 什么是STL&#xff1f; STL有哪些版本&#xff1f; string string的使用&#xff1a; string st1 st2 "北山口镇"​编辑 string st3 "巩义市" string st4(10, *) cout << st1 << endl string st6(st2); string st7 st2; …

0086 Java核心技术卷I Chapter05

目录 5.6枚举类 5.7 反射 5.7.1 Class类 4.7.2 声明异常入门 5.7.3 资源 5.7.4 利用反射分析类的能力 5.7.5 使用反射在运行时分析对象 5.7.6 使用反射编写泛型数组代码 5.7.7 调用任意方法和构造器 5.8 继承的设计技巧 5.6枚举类 public enum Size{SMALL,…

Android聚合SDK母包反编译出包教程

文章目录【前言】一、SDK预处理1、SDK资源合并1.1、合并res目录下的资源1.2、合并libs目录1.3、合并assets目录1.4、合并AndroidManifest.xml1.5、合并jar2、jar转smali2.1、jar 混淆合并2.2、jar转dex2.3、dex转smali二、母包apk反编译1、删除母包模板代码1.1、删掉母包SDK相关…

初识C++ (五)

作者&#xff1a;小萌新 专栏&#xff1a;初阶C 作者简介&#xff1a;大二学生 希望能和大家一起进步 内容简介&#xff1a;本文会简单的介绍auto关键字 还有nullptr关键字 加油&#xff01; 初识Cauto关键字c语言之前的用法C中的新用法auto使用细则auto不能使用的场景1. 未初…

直击固定资产管理痛点,让企业轻松管理海量固定资产

随着固定资产数量和种类、人员、分支机构越来越多&#xff0c;固定资产管理难度加大。传统人工表格管理固定资产的方式在具体实施过程中&#xff0c;会有种种痛点。 1)资产种类繁多&#xff0c;存放地分散且人员变更频繁&#xff0c;管理难度大。 2)盘点费时费力&#xff0c;手…

云原生Kubernetes 基本概念和术语

一、概述 Kubernetes 中的大部分概念如 Node 、 Pod 、 Replication Controller 、 Service 等都可以看作一 种“资源对象”&#xff0c;几乎所有的资源对象都可以通过 Kubernetes 提供的 kubectl 工具&#xff08;或者 API 编程调用&#xff09;执行增、删、改、查等操作并将…

工作中何如来合理分配核心线程数?

文章目录一 回顾1.1 使用线程池的优点1.2 任务类型1.3 IO密集型任务确定线程数1.4 CPU密集型任务确定线程数1.5 混合型任务确定线程数一 回顾 1.1 使用线程池的优点 降低资源消耗&#xff1a;线程是稀缺资源&#xff0c;如果无限制地创建&#xff0c;不仅会消耗系统资源&…

学习笔记-Windows 安全

Windows 安全 注 : 笔记中拓扑图 drawio 源文件在其图片目录下 免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关. 大纲 漏洞利用 LOL RDP 命令行开启RDP多开连接记录凭据窃取绕过组策略限制绕过本地安全策略限制…