OAuth是一种开发受权的网络标准,全拼为open authorization,即开放式受权,最新的协议版本是2.0。javascript
举个栗子:html
有一个"云冲印"的网站,能够将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取本身储存在Google上的照片。java
传统方法是,用户将本身的Google用户名和密码,告诉"云冲印",后者就能够读取用户的照片了。这样的作法有如下几个严重的缺点。node
- "云冲印"为了后续的服务,会保存用户的密码,这样很不安全。
- Google不得不部署密码登陆,而咱们知道,单纯的密码登陆并不安全。
- "云冲印"拥有了获取用户储存在Google全部资料的权力,用户无法限制"云冲印"得到受权的范围和有效期。
- 用户只有修改密码,才能收回赋予"云冲印"的权力。可是这样作,会使得其余全部得到用户受权的第三方应用程序所有失效。
- 只要有一个第三方应用程序被破解,就会致使用户密码泄漏,以及全部被密码保护的数据泄漏。
因此OAuth就诞生了!git
- Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。
- HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的Google。
- Resource Owner:资源全部者,本文中又称"用户"(user)。
- User Agent:用户代理,本文中就是指浏览器。
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,能够是同一台服务器,也能够是不一样的服务器。
登陆层提供令牌(token)的生成,其中token包括:有效期、权限范围。客户端拿到token去访问受限资源。github
- access_token:请求资源时须要携带的token,即访问token。
- refresh_token:刷新token,若是access_token过时,可使用该token获取一份新的access_token和新的refresh_token。通常refresh_token时效性较长,好比一年,而access_token时效性较短,好比几分钟。
- 权限范围:即指定客户端能够获取的资源权限范围。
OAuth有四种受权模式,分别为:json
- 受权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
受权码模式是最为严密的受权模式,总体流程为:浏览器携带必要信息至受权页面,正常登陆成功后,返回一个code(受权码),客户端拿到code后在后台获取拿code换取token。浏览器
密码模式,简单地理解即为使用用户名密码等参数获取access_token,它的步骤以下:安全
- 用户向客户端提供用户名和密码。
- 客户端将用户名和密码发给认证服务器,向后者请求令牌。
- 认证服务器确认无误后,向客户端提供访问令牌。
refresh_token被用来获取新的access_token和refresh_token,使用方式简单以下:服务器
refresh_token无效:
技术栈:
- nodejs + eggjs
- eggjs-oAuth-server插件
具体能够参考:
https://github.com/Azard/egg-oauth2-server
https://cnodejs.org/topic/592b2aedba8670562a40f60b
这里咱们构建两个站点,一个是7001端口(受权服务),一个是7002端口(客户端),受权模式为code grant。
首先是客户端登陆页:
单击按钮后直接登陆:
能够发现,浏览器重定向到受权服务地址,并携带了response_type、client_id、redirect_uri三个参数,登陆成功后,浏览器会重定向到redirect_uri指定的地址,即这里的http://127.0.0.1:7002/auth/redirect:
以下为受权服务的登陆页写法
<form action="/oauth2/authorize?{{query}}" id="form1" name="f" method="post"> <div class="input_outer"> <span class="u_user"></span> <input name="username" class="text" style="color: #FFFFFF !important" type="text" placeholder="请输入帐户"> </div> <div class="input_outer"> <span class="us_uer"></span> <input name="password" class="text" style="color: #FFFFFF !important; position:absolute; z-index:100;"value="" type="password" placeholder="请输入密码"> </div> <div class="mb2"><a class="act-but submit" href="javascript:;" onclick="document.getElementById('form1').submit()" style="color: #FFFFFF">登陆</a></div> </form>
这里的${query}即为客户端登陆重定向携带的完整query,而后是/oauth2/authorize路由的写法:
app.all('/oauth2/authorize', app.oAuth2Server.authorize()); // 获取受权码
这里调用app.oAuth2Server.authorize()时,插件会自动执行重定向操做,首先是重定向到客户端指定地址,客户端拿到code和state后,再去受权层获取token:
async redirect(){ // 服务端重定向过来的 console.log(this.ctx.query) const result = await this.ctx.curl('http://127.0.0.1:7001/users/token', { dataType: 'json', // contentType: 'application/x-www-form-urlencoded', // 默认格式 method: 'POST', timeout: 3000, data: { grant_type: 'authorization_code', code: this.ctx.query.code, state: this.ctx.query.state, client_id: client_id, client_secret: client_secret, redirect_uri: redirect_uri, } }); this.ctx.body = result.data; }
获取到token后正常返回:
首先使用username、password获取access_token:
用户名或密码错误时返回:
使用token获取受权资源正常返回:
以上内容完整源码参考:https://github.com/caiya/eggjs-oAuth2-server
- OAuth实际使用时要上https,包括客户端和受权服务端
- 受权服务可使用私钥签名,客户端使用公钥验证,从而保证数据安全性