OAuth2.0与前端无感知token刷新实现
前言
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刷新实现的更多相关文章
- 基于OAuth2.0的token无感知刷新
目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...
- OAuth2.0的refresh token
最近看人人网的OAuth认证,发现他是OAuth2.0,之前一直看的是新浪的OAuth,是OAuth1.0. 二者还是有很多不同的,主要的不同点在access token的获取方式. OAuth1.0 ...
- 【转】OAuth2.0的refresh token
转载自http://www.html-js.com/?p=1297 最近看人人网的OAuth认证,发现他是OAuth2.0,之前一直看的是新浪的OAuth,是OAuth1.0. 二者还是有很多不同的, ...
- iOS实现OAuth2.0中刷新access token并重新请求数据操作
一.简要概述 OAuth2.0是OAuth协议的下一版本,时常用于移动客户端的开发,是一种比较安全的机制.在OAuth 2.0中,server将发行一个短有效期的access token和长生命期的r ...
- Oauth2.0(三):Access Token 与 Refresh Token
access token 是客户端访问资源服务器的令牌.拥有这个令牌代表着得到用户的授权.然而,这个授权应该是临时的,有一定有效期.这是因为,access token 在使用的过程中可能会泄露.给 a ...
- Oauth2.0:Access Token 与 Refresh Token
access token 是客户端访问资源服务器的令牌.拥有这个令牌代表着得到用户的授权.然而,这个授权应该是临时的,有一定有效期.这是因为,access token 在使用的过程中可能会泄露.给 a ...
- OAuth2.0和企业内部统一登录,token验证方式,OAuth2.0的 Authorization code grant 和 Implicit grant区别
统一登录是个很多应用系统都要考虑的问题,多个项目的话最好前期进行统一设计,否则后面改造兼容很麻烦: cas认证的方式:新公司都是老项目,用的是cas认证的方式,比较重而且依赖较多,winform的项目 ...
- OAuth2.0 微博登陆网站功能的实现(一)获取用户授权及令牌 Access Token
在登陆一些网站的时候,可以选择登陆方式为第三方登陆,例如微博登陆,以爱奇艺为例,进入首页,点击 ”登陆“,会弹出登录框: 除了本站登陆外,还可以选择其他第三方登陆,比如微博登陆.QQ 登陆.微信登陆等 ...
- ASP.NET WebApi 基于OAuth2.0实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...
随机推荐
- 【原创】Linux PCI驱动框架分析(二)
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- 用漫画的形式展现——什么是web
Web主要经历了web1.0和web2.0的阶段.万维网的初期都是web1.0的时代:静态页面. 在不同的时代,流行的web安全问题也不太相同.在web1.0时代,web安全主要是 Web1.0:sq ...
- Vue 面试题总结
1. Vue 框架的优点是什么? (1)轻量级框架:只关注视图层,大小只有几十Kb: (2)简单易学:文档通顺清晰,语法简单: (3)数据双向绑定,数据视图结构分离,仅需操作数据即可完 ...
- jQuery EasyUI学习一
1. jQuery EasyUI介绍 1. 创建组件的方式和原理(掌握) 2. 组件三要素(掌握) 3. Panel.LinkButton.上下文菜单;(掌握) 简介 2.1. jQuer ...
- ORB-SLAM3 细读单目初始化过程(上)
作者:乔不思 来源:微信公众号|3D视觉工坊(系投稿) 3D视觉精品文章汇总:https://github.com/qxiaofan/awesome-3D-Vision-Papers/ 点击上方&qu ...
- Java入门随手记-DOS命令
DOS 打开cmd的方式 开始+系统+命令提示符 win键+r 输入cmd打开控制台(推荐使用) 在任意的文件夹下面,按住shift键+鼠标右键点击,在此次打开命令窗口 资源管理器的地址栏前面加上cm ...
- RedHat6.1通过配置yum server安装软件包
1.获取镜像RHEL_6.1\ x86_64\ Disc\ 1 2.配置yum server #cd /etc/yum.repos.d #tar -cvf 20141114bak.tar *.repo ...
- 内存性能测试 Memtester+mbw
Memtester简单介绍 Memtester主要是捕获内存错误和一直处于很高或者很低的坏位, 其测试的主要项目有随机值,异或比较,减法,乘法,除法,与或运算等等. 通过给定测试内存的大小和次数, 可 ...
- 【Java】流程控制 - 顺序结构、 选择(分支)结构(单分支、双分支、多分支、嵌套)、循环结构(for、while、do...while)、跳转语句(break、continue)
流程控制语句结构 文章目录 流程控制语句结构 一. 顺序结构 1. 输出语句 2. 输入语句 3.code 二.复合语句 三. 分支结构 1. 条件判断 1.单分支结构 2.双分支结构 3.多分支结构 ...
- ps 2020 下载
一款极具实用价值的作图软件--ps,由于正版价格昂贵,所以这里分享破解版的资源.b话少说,下面是下载链接和安装步骤: 下载链接: 百度网盘链接:https://pan.baidu.com/s/1XPf ...