前言

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛的应用。Facebook、Twitter和Google等各种在线服务都提供了基于OAuth规范的认证机制。

OAuth一般用于面向第三方大范围公开的API中的认证工作。换言之,假设带有用户注册功能的在线服务A(例如腾讯qq)对外公开了API,在线服务B(例如百度网盘)便可使用这些在线服务A的API提供的各种功能。这种情况下,当某个已在qq里注册的用户需要百度网盘的在线服务时,网盘的在线服务就会希望访问qq来使用该用户信息。这时,判断是否允许网盘使用该用户在qq里注册的信息的机制就是OAuth。 

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

2、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

3、以查询参数的形式添加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
 

OAuth2.0与前端无感知token刷新实现的更多相关文章

  1. 基于OAuth2.0的token无感知刷新

    目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...

  2. OAuth2.0的refresh token

    最近看人人网的OAuth认证,发现他是OAuth2.0,之前一直看的是新浪的OAuth,是OAuth1.0. 二者还是有很多不同的,主要的不同点在access token的获取方式. OAuth1.0 ...

  3. 【转】OAuth2.0的refresh token

    转载自http://www.html-js.com/?p=1297 最近看人人网的OAuth认证,发现他是OAuth2.0,之前一直看的是新浪的OAuth,是OAuth1.0. 二者还是有很多不同的, ...

  4. iOS实现OAuth2.0中刷新access token并重新请求数据操作

    一.简要概述 OAuth2.0是OAuth协议的下一版本,时常用于移动客户端的开发,是一种比较安全的机制.在OAuth 2.0中,server将发行一个短有效期的access token和长生命期的r ...

  5. Oauth2.0(三):Access Token 与 Refresh Token

    access token 是客户端访问资源服务器的令牌.拥有这个令牌代表着得到用户的授权.然而,这个授权应该是临时的,有一定有效期.这是因为,access token 在使用的过程中可能会泄露.给 a ...

  6. Oauth2.0:Access Token 与 Refresh Token

    access token 是客户端访问资源服务器的令牌.拥有这个令牌代表着得到用户的授权.然而,这个授权应该是临时的,有一定有效期.这是因为,access token 在使用的过程中可能会泄露.给 a ...

  7. OAuth2.0和企业内部统一登录,token验证方式,OAuth2.0的 Authorization code grant 和 Implicit grant区别

    统一登录是个很多应用系统都要考虑的问题,多个项目的话最好前期进行统一设计,否则后面改造兼容很麻烦: cas认证的方式:新公司都是老项目,用的是cas认证的方式,比较重而且依赖较多,winform的项目 ...

  8. OAuth2.0 微博登陆网站功能的实现(一)获取用户授权及令牌 Access Token

    在登陆一些网站的时候,可以选择登陆方式为第三方登陆,例如微博登陆,以爱奇艺为例,进入首页,点击 ”登陆“,会弹出登录框: 除了本站登陆外,还可以选择其他第三方登陆,比如微博登陆.QQ 登陆.微信登陆等 ...

  9. ASP.NET WebApi 基于OAuth2.0实现Token签名认证

    一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...

随机推荐

  1. 敏捷史话(一):用一半的时间做两倍的事——Scrum之父Jeff Sutherland

    普通的人生大抵相似,传奇的人生各有各的传奇.Jeff就是这样的传奇人物,年近80的他从来没有"廉颇老矣尚能饭否"的英雄迟暮,不久前还精神矍铄地与好几百名中国学生进行线上交流,积极回 ...

  2. CentOS7离线安装mysql5.6

    下载mysql5.6,系统选择redhat,版本选择RHEL7,下载RPM Bundle后得到一个tar文件.这里得到文件MySQL-5.6.44-1.el7.x86_64.rpm-bundle.ta ...

  3. 动态SQL基本语句用法

    1.if语句 如果empno不为空,则在WHERE参数后加上AND empno = #{empno},这里有1=1所以即使empno为null,WHERE后面也不会报错. 映射文件 <selec ...

  4. 【代码周边】-GitHub笔记

    ------------恢复内容开始------------ 程序员的宝库github是个好东西,其中开源的项目足够我们的使用,但是如何去精准的获取我们的项目是很多初学者的问题.特别是英语不好的我,一 ...

  5. js对比两个时间的大小

    /** * 时间对比 开始=结束返回0;开始>结束返回-1;开始<结束返回1 */ function dateComparison(date1,date2){ var start =new ...

  6. CentOS 搭建 SVN 服务器 及使用教程

    服务器与客户端 1.搭建SVN服务器 ① CentOS安装SVN 命令: yum -y install subversion 检查是否安装成功 命令: svn --version 如果显示如下内容说明 ...

  7. 基于ESP32的智能家居管理系统的设计与实现

    基于ESP32的智能家居管理系统的设计与实现 ESP32的智能家居管理系统访问链接: https://www.cnblogs.com/easyidea/p/13101165.html 一.需求分析 1 ...

  8. windows server 2012 R2里IIS配置.net core2.1遇到的坑

    首先刚接触.net core不久,在本地也是简单写点测试程序,没遇到过什么问题,感觉还行,最近朋友搞了个asp.net core2.1的程序,让我给他服务器配置一下,我想这都跨平台了有什么难的吗?拿来 ...

  9. 进制及其字符串之间互转——C#

    本文介绍进制数转进制数,及每个进制对应的字符串 一.首先进制数转进制数(int-->int) 1.二进制数与十进制数互转: (1)二进制数转十进制数:还没找到 (2)十进制数转二进制数:目前还没 ...

  10. netcore.ydal可能是东半球最好ORM框架

    typora-copy-images-to: static ydal 项目框架介绍 可能是东半球最好netcore ORM框架,目前暂时支持mysql.mssql,其核心设计目标是开发迅速.学习简单. ...