推广

OAuth2.0与前端无感知token刷新实现

iseeyu2年前 (2024-02-22)推广135

示意图

OAuth

OAuth的关键是,在使用百度网盘的在线服务时,用户无需再次输入qq的密码。为了实现这一机制,认证过程中会通过qq提供的Web页面,让用户确认是否允许访向百度网盘的在线服务提供qq账户信息。如果尚未登录qq,则需要用户输入密码,这一过程也是只在qq里完成登录,并不会把密码发送给百度网盘的在线服务。

示意图

如果通过OAuth访问成功,网盘就可以从qq中获取一个名为access token的令牌。通过该token,便可访问qq中用户允许访问的信息。

OAuth最主要的优点在于它是一种被广泛认可的认证机制,并且已经实现了标准化。

OAuth2.0的认证流程

Grant Type 作用
Authorization Code 适用于在服务端进行大量处理的web应用
Implicit 适用于智能手机应用及使用JavaScript客户端进行大量处理的应用
Resource Owner Password Credential 适用于不使用服务端的应用
Client Credentials 适用于不以用户为单位来进行认证的应用

其中Resource Owner Password Credential模式就是不存在网站B,客户端直接从用户那里得到密码,并从服务器A那里获取access token。这一授权模式就能够应用在公司内部所开发的客户端应用中。

使用Resource Owner Password Credential模式进行认证时,在访问API时需要将参数以application/x-www-form-urlencoded的形式(也就是表单的形式),进行UTF-8字符编码后向服务器发送

键值(key) 内容
grant_type 字符串password。表示使用了Resource Owner Password Credential
username 登录的用户名
password 登录的密码
scope 指定允许访问的权限范围

最后的scope一栏用来指定允许访问的权限范围。权限范围的名称可以由在线服务独自定义,可以使用除空格、双引号、反斜杠以外的所有ASCII文本字符。通过使用scope,就能在外部服务(在线服务B)获取token的同时对允许访问的范围进行限制,还能向用户显示“该服务会访问以下信息”等提示。虽然scope不是必选项,但还是建议事先定义好。

示例:

POST /v1/oauth2/token HTTP/1.1
Host: api.example.com
Authorization: Basic XXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=zhang&password=zhang&scope=api

示例请求中还附加Authorization首部,称为客户端认证(Client Authorization)。它用来描述需要访问的服务(即在线服务B)是谁。

在应用登录在线服务时,这些服务就会向其发行Client ID和Client Secret视为用户名/密码,并以Basic认证的形式经Base64编码后放入Authorization首部。Client ID和Client Secret可以任意使用,服务器端可以依据这些信息识别出当前访问的服务的应用身份。比如服务端对各个应用访问API的次数进行限制时,或者希望屏蔽一些未经授权的应用时,就可以使用Client ID和Client Secret。

当正确的信息送达服务器后,服务器端便会返回如下JSON格式的响应:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pargma: no-cache

{
    "access_token": 'zskldjflsdjflksjdflkjsd'
    "token_type": "bearer",
    "expires_in": 2629743,
    "refresh_token": 'ajsldkjflskdfjldfg'
}

token_type中的bearer是RFC6750中定义的OAuth2.0所用的token类型。access_token是以后访问时所需的access token。在以后访问API时,只需附带发送该token信息即可。这时无需再次发送ClientID和ClientSecret信息了。因为各个不同的客户端都会从服务器端得到特定的access token,即使之后没有ClientID,服务端也同样可以用access token信息来识别应用身份。

根据RFC6750的定义,客户端有3种方法将bearer token信息发送给服务器端:

  • 添加到请求信息的首部
  • 添加到请求消息体
  • 以查询参数的形式添加到URL中
  1. 将token信息添加到请求消息的首部时,客户端要用到Authorization首部,并按下面的形式指定token的内容:
GET /v1/users/ HTTP/1.1
Host: api.example.com
Authorization: Bearer zskldjflsdjflksjdflkjsd

  1. token信息添加到请求消息体中,则需要将请求消息里的Content-Type设定为application/x-www-form-urlencoded,并用access_token来命名消息体里的参数,然后附加上tokan信息
POST /v1/users HTTP/1.1
Host: api.example.com
Context-Type: application/x-www-form-urlencoded

access_token=zskldjflsdjflksjdflkjsd

  1. 以查询参数的形式添加token参数时,可以在名为access_token的查询参数后指定token信息。
GET /v1/users?access_token=zskldjflsdjflksjdflkjsd HTTP/1.1
Host: api.example.com

access token的有效期和更新

客户端在获得access token的同时也会在响应信息中得到一个名为expires_in的数据,它表示当前获得的access token会在多少秒以后过期。当超过该指定的秒数后,access token便会过期。当access token过期后,如果客户端依然用它访问服务,服务端就会返回invalid_token的错误或401错误码。

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "error": "invaild_token"
}

当发生invalid_token错误时,客户端需要使用refresh token再次向服务端申请access token。这里的refresh token时客户端再次申请access token时需要的另一个令牌信息,它可以和access token一并获得。

在刷新access token的请求里,客户端可以在grent_type参数里指定refresh_token,并和refresh_token一起发送给服务器端。

POST /v1/oauth2/token HTTP/1.1
Host: api.example.com
Authorization: Bearer zskldjflsdjflksjdflkjsd
Content-Type: application/x-www-form-urlencoded

grent_type=refresh_token&refresh_tokne=ajsldkjflskdfjldfg

封装Axios实现无感刷新token

  • utils/oauth.js
const TokenKey = 'access_token'
const ExpiresKey = 'expires_in'
const TokenTypeKey = 'bearer'
const RefreshTokenKey = 'refresh_token'

export function getToken () {
  return localStorage.getItem(TokenTypeKey) + ' ' + localStorage.getItem(TokenKey)
}

export function getRefreshToken () {
    return localStorage.getItem(RefreshTokenKey)
}

export function setToken (data) {
  const ExpiresTime = new Date().getTime() + data.expires_in * 1000

  localStorage.setItem(TokenKey, data.access_token)
  localStorage.setItem(ExpiresKey, ExpiresTime)
  localStorage.setItem(TokenTypeKey, data.token_type)
  localStorage.setItem(RefreshTokenKey, data.refresh_token)
}

export function removeToken () {
  localStorage.removeItem(TokenKey)
  localStorage.removeItem(ExpiresKey)
  localStorage.removeItem(TokenTypeKey)
  localStorage.removeItem(RefreshTokenKey)
}

  • request.js
import axios from 'axios'
import Vue from 'vue'
import { removeToken } from '@/utils/oauth'

const server = axios.create({
  baseURL: baseUrl,
  withCredentials: true
})

// 用于记录是否正在刷新token,以免同时刷新
window.tokenLock = false

function refreshToken () {
  if (!window.tokenLock) {
    server.put('/oauth/refresh').then(({data}) => {
      const ExpiresTime = new Date().getTime() + data.expires_in * 1000
      localStorage.setItem('access_token', data.access_token)
      localStorage.setItem('expires_in', ExpiresTime)
      localStorage.setItem('token_type', data.token_type)
      localStorage.setItem('refresh_token', data.refresh_token)
    })
    window.tokenLock = true
  }
}

server.interceptors.request.use(req => {
  req.headers['Authorization'] = `${localStorage.getItem('token_type')} ${localStorage.getItem('access_token')}`
  return req
}, error => {
  return Promise.reject(error)
})

server.interceptors.response.use(rep => {
  // 如果距离过期时间还有10分钟就使用refresh_token刷新token
  const expiresTimeStamp = new Date(Number(localStorage.getItem('expires_in'))).getTime() - new Date().getTime()
  if (expiresTimeStamp < 10 * 60 * 1000 && expiresTimeStamp > 0) {
    if (rep.config.url.indexOf('current') < 0) {
      refreshToken()
    }
  }
  return rep
}, error => {
  if (error.response.status === 401) {
    // 401错误:token失效或登录失败
    // 如果是在登录页报错的话直接显示报错信息,否则清除token
    if (location.href.indexOf('login') > 0) {
      Vue.prototype.$notify.error({
        title: '错误',
        message: error.response.data.message
      })
      return
    }
    removeToken()
    location.reload()
  }
  return Promise.reject(error)
})

export default server

作者: zhangwinwin
链接:OAuth2.0与前端无感知token刷新实现
来源:github

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://0291.com.cn/post/56170.html

相关文章

造势不如借势-看京东如何做整合营销的

京东赞助《我是歌手》大家应该不陌生,刚开始很多人都在吐槽电视的衰败,但毋庸置疑的是电视还是一如既往,源源不断的提供的一线始端。正如界的人说:“内容为王,外链为皇”胡水生个人认为:“中央电视台代表着为王,热门节目代表着内容为王”。当然,赞助节目的选择也不是那么容易的。笔者大致...

提升搜索引擎友好度要改进的事情。

提升搜索引擎友好度要改进的事情。

搜索引擎从网站结构中抓取网站内容。为了提高搜索引擎的友好性,优化网站结构非常重要。与用户一样,搜索引擎需要投入才能获得友好性。 div+css合理布局 div+css布局的网站代码相对简化,加载速度快。它可以攻击搜索引擎友好的基础知识,网站集和排名会很高。但是,div+css布局的...

分享SEO全面分析,优化究竟从何下手。

分享SEO全面分析,优化究竟从何下手。

小编今天要讲的是拿到一个网站做优化要从哪些地方下手。很多人认为做优化都是网站上线以后才开始的工作,其实不对的,网站服务器(或是虚拟主机)、后台系统、前端代码、网站架构等队友优化都有着一定的影响。 一、网站服务器或是虚拟主机 这一块主要是对网站的打开速度和稳定性的影响,网...

网站退出率和跳出率之间有什么关系

网站退出率和跳出率之间有什么关系

本质上这两个指标间并没有什么关系。咱们先来了解下退出率。 退出率,通俗指的是用户退出的次数除以用户进入浏览网站的次数的百分比。 一般而言,一个网站可能有不同的链接导向到网站的不同网页里,这些不同的链接就是这个网站的不同入口。当访客由这些不同的网站入口进入这网站时,这个动作就称为”进入网站”,而他...

我来分享杭州SEO优化:什么样的外链才是高质量的外链。

我来分享杭州SEO优化:什么样的外链才是高质量的外链。

什么样的外链高质量的外链?一般情况下,评判外链质量的高低是有六大标准可以参照的。下面seo优化就来说一说这些标准,希望各位新手站长能够更加深入的了解发布外链与seo优化知识。 标准一、外链的正规性 大家做优化肯定都发过外链,这是一项非常枯燥的工作,有的站长耐不住寂寞,...

不得不学的销售技巧

不得不学的销售技巧

#奇妙季#做销售的,只要你学会发朋友圈的这六个金句,能够帮你留住90%的客户朋友,用好这六句话,起码帮你省下2000块钱的费用,我很多同事都说,简直太好用了,真的不可思议,接下来第一句表现你的敬业要怎么说,一日未脱贫,卖货不能停,万物皆可爱,有货一直卖第二句表现你的真诚要怎...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片