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数 ...
随机推荐
- CentOS7 实战部署tomcat网站服务器
简介:实战演练tomcat网站服务器的搭建 Tomcat:是一个开源免费的Web应用服务器,性能稳定,是目前比较流行的Web应用服务器 tomcat官网下载: https://tomcat.apa ...
- sql 中 foreach 中传入多个不同的参数问题
<!--查找某用户绑定的药物不良反应报告列表--> <select id="selectSurveyListByUserProId" resultType=&qu ...
- 开源编解码项目FFmpeg迎来20周年生日 凭一己之力养活全球无数播放器!
近日,开源编解码库项目FFmpeg迎来20周年生日. 2000.12.20-2020.12.20 可能很多人对于FFmpeg不是特别了解,那么以下几个名字是否大家或多或少都用过呢? 暴风影音.PotP ...
- 关于IP的相关计算
不论是考研还是考各种计算机类的证,大家或多或少都会遇到网络部分的一种题型,大体的归类就是以下几种: 已知一个IP是192.XX.XX.XX,子网掩码是255.255.255.0,那么它的网络地址是多少 ...
- DW数据仓库
https://blog.csdn.net/bjweimengshu/article/details/79256504 from Wikipedia 在计算机科学中,数据仓库(data warehou ...
- Kafka知识总结及面试题
目录 概念 Kafka基础概念 命令行 Kafka 数据存储设计 kafka在zookeeper中存储结构 生产者 生产者设计 消费者 消费者设计 面试题 kafka设计 请说明什么是Apache K ...
- Java并发编程实战(3)- 互斥锁
我们在这篇文章中主要讨论如何使用互斥锁来解决并发编程中的原子性问题. 目录 概述 互斥锁模型 互斥锁简易模型 互斥锁改进模型 Java世界中的互斥锁 synchronized中的锁和锁对象 synch ...
- LeetCode841 钥匙和房间
有 N 个房间,开始时你位于 0 号房间.每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间. 在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i ...
- Redis Cluster 集群节点维护 (三)
Redis Cluster 集群节点维护: 集群运行很久之后,难免由于硬件故障,网络规划,业务增长,等原因对已有集群进行相应的调整,比如增加redis nodes 节点,减少节点,节点迁移,更换服务器 ...
- 万字长文爆肝 DNS 协议!
试想一个问题,我们人类可以有多少种识别自己的方式?可以通过身份证来识别,可以通过社保卡号来识别,也可以通过驾驶证来识别,尽管我们有多种识别方式,但在特定的环境下,某种识别方法可能比另一种方法更为适合. ...