app基于OAuth2.0 协议构建授权登录系统

2,967 阅读5分钟

前言

对于OAuth2.0,后端同学或多或少都有了解,对于前端同学,可能了解的少一些,但它却充斥在我们的日常生活中,例如微信qq微博等第三方授权登录。曾经你用到了,但不曾了解过它,今天我们就拨开其朦胧面纱,了解本质,并阐述其在公司内部的实践与应用。

OAuth2.0是什么

OAuth2.0是一个开放标准,允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。

为什么用OAuth2.0

对于这个话题,可以从两个角度来阐述

使用第三方授权登录

对于需要推广的应用,一般都会提供第三方授权登录,比如掘金,提供了微博微信Github等登录方式,这样做的好处是,用户本身就有微信Github账号,不需要再次进行注册,不需要提供邮箱或者手机号,就可以快速实现登录,有助于更快,更稳定的获取用户。

image.png

使用公司内部认证服务

随着公司业务的发展,架构的演进,搭建公司内部认证服务可能是一种趋势,当然这只是我个人的不成熟见解。谈谈我们公司的一些情况,在一开始每个项目各自为战,新建一个项目,就需要提供一个登录页面,不同的团队有不同的架构体系,一些团队使用的cookie,一些团队使用的token,但这多套体系还是由我们架构部来进行维护,需要投入极大的人力成本。后来又需要做平台化,前端使用的是微前端架构集成多个系统,后端微服务架构,这就需要提供统一的用户中心。随着架构的演进,实现了公司内部的认证中心,实现一次登录,可以访问所有的服务,也统一了用户登录。

怎么使用OAuth2.0

对于oAuth2.0的具体使用,我们也是从两个维度来阐述,一方面是第三方授权登录,一方面是公司内部认证授权登录。第三方授权登录,我们以较为常用的微信登录作为示例,微信登录也是OAuth2.0的典型运用场景。

微信授权登录流程

  1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据 code 参数;
  2. 通过 code 参数加上 AppID 和AppSecret等,通过 API 换取access_token;
  3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

image.png

公司内部授权登录流程

公司内部授权登录流程和微信授权登录的总体流程是一致的,但具体实现还是不一样的,具体的差别有两点,1、使用微信登录的用户,都是微信用户,所以直接点击确认按钮或者微信扫码就可以实现登录,但是我们没有这样子的条件,所以还是需要账号密码的方式进行登录。2、微信登录是唤起微信应用进行授权,我们只能借助于浏览器跳转我们的登录页面。

  1. 用户发起登录请求,重定向到我们定义好的登录页面,实现账号密码登录,登录完成时重定向回应用,并在url上附带授权临时票据code参数。
  2. 通过 code 参数加上其他定制参数,通过 API 换取access_token;
  3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

image.png

app授权登录

考虑到人力成本,目前采用uni-app实现跨端,所以我就以uni-app开发的app作为示例,我们的应用都是必须登录才能使用,如果不是这样的模式,只是多了需要主动点击授权登录。

具体流程

image.png

校验是否登录

需要一个接口用于校验是否登录,未登录时,需要跳转到登录页,这个登录页其实不是真正的登录页,只是一个webView,用于为认证登录页面提供浏览器环境。

// 获取当前页面路由
const handleRoute = () => {
  const list = getCurrentPages();
  const index = list.length - 1;
  return list.length > 0 ? `/${list[index].route}` : '';
};
try {
  // 调用检测登录接口
  const data = await checkLogin();
  const { url, code } = data || {};
  // 如果code为403即未登录
  if (code === 403) {
    // 获取认证登录页路径(app需要使用此接口,h5直接走302跳转就可以了)
    const { locationUrl } = await getLocationUrl(url);
    // 获取当前页路由,用于登录完成返回当前页
    const path = handleRoute();
    // 路由传递参数字段太长需要用encodeURIComponent转码
    const params = encodeURIComponent(JSON.stringify({ locationUrl, path }));
    const loginPath = '/pages/login/index';
    // 跳转到存放webView的登录页,并且传递当前页面路由和认证登录页的路径
    uni.$u.route(loginPath, { params });
  }
} catch (err) {
  return Promise.reject(err);
}

登录页

这个页面算是承上启下,起到了中间桥梁的作用,一方面,利用web-view标签,提供浏览器运行环境,另一方面,读取重定向返回的code,向认证服务换取access_token

image.png

<template>
  <view class="page">
    <web-view :src="params.url" v-if="params.url"></web-view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      params: {},
    };
  },
  async onShow() {
    // 唤醒app的传递过来的参数
    const args = plus.runtime.arguments;
    if (args) {
      // 获取路径?之后的参数,包含code和定制参数
      const params = this.getUrlParams(args);
      // 获取上一页的路由
      const { path } = this.params;
      try {
        // 根据code以及其他定制参数换取access_token
        await getAuthorization({ params });
        // 跳转回上一页,这样做的原因是解决上一页可能是tabbar的问题
        uni.navigateTo({
          url: path,
          fail: () => {
            uni.switchTab({
              url: path,
              fail: (err) => {
                console.error('login page return failed', err);
                uni.showToast({
                  title: err.message,
                  icon: 'none',
                });
              },
            });
          },
        });
      } catch (err) {
        uni.showToast({
          title: err.message,
          icon: 'none',
        });
      }
    }
  },
  onLoad({ params }) {
    // 获取路由传递的参数
    this.params = JSON.parse(decodeURIComponent(params));
  },
  methods: {
    // 获取参数
    getUrlParams(href) {
      return href.split('?')[1];
    },
  },
};

认证登录页唤起应用

app对接oAuth2.0认证登录的难点就是在登录完成时,怎么通过后端302重定向将code传递给应用。要实现这个功能需要用scheme协议,为当前应用配置UrlSchemes,这个类似于app的名字,不过是独一无二,如果重复的话,会同时唤起多个app,有了这个名字后,就可以像访问网站一个访问对应的app,只是这个路径比较特殊而已,例如我的UrlSchemes为test,那么我可以直接在浏览器访问test://唤起我的应用。

image.png

小结

对于oAuth2.0认证授权登录,我也只是刚刚应用,这边分享出来,可以为不了解oAuth2.0的同学扩展一下视野,为正在对接oAuth2.0的同学提供一些思路,毕竟我也算是走在了路上,对于熟悉oAuth2.0的同学,有不足之处或者有好的观点可以分享一下,让我们在技术探讨中学习、成长,变得更强。