OneDrive第三方登录 -「Koa」

1,643 阅读5分钟

前提

老师布置一个SS级任务 -> 做一个微软OneDrive网盘
那就做叭!期间遇到很多很多坑!
查阅了很多方法,感觉最后总体还是很粗糙!
但起码做了出来,做个记录!
也希望有大佬不吝赐教

接口介绍

登录并获取授权码

GET 
https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}

必需的查询字符串参数

参数名称描述
client_id为您的应用创建的客户端ID。
response_type指定请求的响应类型。在授权码授予请求中,该值必须是code。
redirect_uri身份验证完成后,浏览器将发送到的重定向URL。

响应

在成功验证用户身份并授权您的应用程序后,如下面的示例所示,Web浏览器将重定向到您的重定向URL,并在URL中添加了其他参数。

https://myapp.contoso.com/myapp/callback?code=AwABAAAAvPM...

code为下一步的授权码。

兑换令牌的授权代码

POST 
https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded

body:
client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
&code={code}&grant_type=authorization_code&resource={resource_id} 

必需参数

该请求的主体是URL编码的字符串,带有以下必需参数:

参数名称描述
client_id为您的应用程序创建的客户端ID值。
redirect_uri身份验证完成后,浏览器将发送到的重定向URL。这应该与第一个请求中的redirect_uri相匹配。
client_secret为您的应用程序创建的键值之一。
您在第一个身份验证请求中收到的授权码。
资源您要访问的资源。

响应

如果调用成功,响应主体是包括JSON字符串access_token,expires_in和refresh_token性能。

{
  "expires_in": 3600,
  "access_token":"EwCo...AA==",
  "refresh_token":"eyJh...9323"
}

注意:响应中可能还有其他属性。使用API不需要这些属性。

重要提示:在此响应中,access_token和的值应与refresh_token用户密码一样安全地对待。

该访问令牌仅在expires_in属性中指定的秒数内有效。您可以通过使用刷新令牌或从头开始重复身份验证请求来请求新的访问令牌。

至此就可以获取到access_token数据

应用实现

Koa后端接口

创建文件夹

创建文件夹cloud-disk-koa-api

初始化npm仓库

  1. 打开该文件夹终端
  2. npm init -y(-y: 默认yes配置)

安装插件

npm install axios koa koa-router koa2-cors qs -S

创建启动文件

// server.js

const Koa = require('koa')
const app = new Koa()
const router = require('koa-router')()
const cors = require('koa2-cors')
const axios = require('axios')
const qs = require('qs')

// 开启跨域
// 前端vue跨微软域名失败
app.use(cors())

// 获取code接口
router.get('/code', async (ctx, next) => {
  // oneDrive配置
  const oneDriveConfig = {
    client_id: '你的应用ID',
    scope: 'offline_access Files.Read Files.Read.All',
    response_type: 'code',
    // 重定向回后端获取token接口
    redirect_uri: 'http://localhost:7001/token'
  }
  let path = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${oneDriveConfig.client_id}&scope=${oneDriveConfig.scope}&response_type=${oneDriveConfig.response_type}&redirect_uri=${oneDriveConfig.redirect_uri}`
  // 拼接链接,返回前端进行登陆获取code跳转
  ctx.body = path
})

// 获取token接口
router.get('/token', async (ctx, next) => {
  // 重定向回前端页面
  const redirectPath = 'http://localhost:2020/login'
  // 获取到 /code返回的code值
  const { code } = ctx.query
  // oneDrive配置
  const oneDriveConfig = {
    grant_type: 'authorization_code',
    client_id: '你的应用ID',
    redirect_uri: 'http://localhost:7001/token',
    client_secret: '你的应用密匙',
    code
  }
  const path = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
  // axios post请求接口
  // 利用qs将body的json格式转为form-data格式
  const result = await axios.post(path, qs.stringify(oneDriveConfig), {
    // 设置请求头配置 - form-data格式
    headers: {
      'Accept': '*/*',
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  })
  // 获取到access_token数据
  const token = result.data.access_token
  // 拼接token值重定向回前端登陆页面
  ctx.redirect(`${redirectPath}?token=${token}`)
})

// 开启路由
app.use(router.routes(), router.allowedMethods())

// 开启7001端口
app.listen(7001, () => {
  console.log('run on 7001')
})

开启服务

node ./server.js

开启成功

终端打印 run on 7001

vue前端

思路

  1. 开启指定http://localhost:2020端口的前端

  2. 创建http://localhost:2020/login路由前端页面

  3. login页面get请求http://localhost:7001/code返回到的登录拼接url

  4. 跳转到微软登陆拼接的url

    1. location.href = 登录拼接url;
      
  5. 等待后端token接口获取到token后重定向回http://localhost:2020/login页面

  6. 截取token值保存到浏览器cookie中

  7. 去掉地址栏中的token参数

    1. window.location.href = 去掉token参数的前端url
      

有个注意

  1. 正浏览B页面(非login登录页面) -> 是当token过期时 -> 显示弹框token过期 -> 跳转回login页面 -> 一顿思路操作后返回token保存到cookie -> 重定向回B页面(而非在login登录页面停留)

  2. 第一次登录(处于login登录页面时) -> 一顿思路操作后返回token保存到cookie -> 重定向回首页

感兴趣可以做一下我觉得是一个必要需求(有点难度)

实现:利用vuex和vue-router的路由守卫

// 主要路由守卫判断代码

import router from './router'
import store from './store'
// 各种浏览器cookie的操作
import { getToken, getRedirect, removeRedirect } from '@/utils/auth' // get token from cookie
// 获取浏览器标题title
import getPageTitle from '@/utils/get-page-title'

const whiteList = ['/login'] // no Redirect whitelist

router.beforeEach(async(to, from, next) => {
  // set page title
  document.title = getPageTitle(to.meta.title)
  // determine whether the user has logged in
  const hasToken = getToken()
  const hasRedirect = getRedirect()
  if (hasToken) {
    if (hasRedirect && to.path !== hasRedirect) {
      await router.push(hasRedirect)
    } else {
      await removeRedirect()
      next()
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      if (!hasRedirect) {
        await store.dispatch('user/redirect', to.path)
      }
      next('/login')
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})


后记

其实也没啥后记
但也不能草草结尾呀对吧对吧!


厚着脸皮的林大人的小屋系列: