zoukankan      html  css  js  c++  java
  • nodejs实现OAuth2.0授权服务

    OAuth是一种开发授权的网络标准,全拼为open authorization,即开放式授权,最新的协议版本是2.0。

    举个栗子:

    有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。

    传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

    • "云冲印"为了后续的服务,会保存用户的密码,这样很不安全。
    • Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。
    • "云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。
    • 用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。
    • 只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

    所以OAuth就诞生了!

    名词定义

    • Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。
    • HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的Google。
    • Resource Owner:资源所有者,本文中又称"用户"(user)。
    • User Agent:用户代理,本文中就是指浏览器。
    • Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
    • Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

    OAuth整体思路

    登录层提供令牌(token)的生成,其中token包括:有效期、权限范围。客户端拿到token去访问受限资源。

    • access_token:请求资源时需要携带的token,即访问token。
    • refresh_token:刷新token,如果access_token过期,可以使用该token获取一份新的access_token和新的refresh_token。一般refresh_token时效性较长,比如一年,而access_token时效性较短,比如几分钟。
    • 权限范围:即指定客户端可以获取的资源权限范围。

    OAuth授权模式

    OAuth有四种授权模式,分别为:

    • 授权码模式(authorization code)
    • 简化模式(implicit)
    • 密码模式(resource owner password credentials)
    • 客户端模式(client credentials)

    1、授权码模式

    授权码模式是最为严密的授权模式,整体流程为:浏览器携带必要信息至授权页面,正常登录成功后,返回一个code(授权码),客户端拿到code后在后台获取拿code换取token。

    code grant模式

    此处输入图片的描述

    2、密码模式

    密码模式,简单地理解即为使用用户名密码等参数获取access_token,它的步骤如下:

    • 用户向客户端提供用户名和密码。
    • 客户端将用户名和密码发给认证服务器,向后者请求令牌。
    • 认证服务器确认无误后,向客户端提供访问令牌。

    3、refresh_token的应用

    refresh_token被用来获取新的access_token和refresh_token,使用方式简单如下:

    refresh_token无效:

    使用nodejs实现OAuth授权服务

    技术栈:

    • nodejs + eggjs
    • eggjs-oAuth-server插件

    具体可以参考:
    https://github.com/Azard/egg-oauth2-server
    https://cnodejs.org/topic/592b2aedba8670562a40f60b

    1、code grant模式测试及单点登录实现

    这里我们构建两个站点,一个是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后正常返回:

    2、password grant模式测试

    首先使用username、password获取access_token:

    用户名或密码错误时返回:

    使用token获取授权资源正常返回:

    以上内容完整源码参考:https://github.com/caiya/eggjs-oAuth2-server

    总结

    • OAuth实际使用时要上https,包括客户端和授权服务端
    • 授权服务可以使用私钥签名,客户端使用公钥验证,从而保证数据安全性
  • 相关阅读:
    Python那些优雅的写法:switch-case
    python将print输出的信息保留到日志文件中
    Python 获取被调用函数名称,所处模块,被调用代码行
    python **运算符及多参数传参
    使用Docker之镜像的拉取、查询、删除
    资料
    kmp算法
    开源软件
    golang之http请求的dns解析代码流程
    Go语言从HTTP请求中获取服务端IP地址
  • 原文地址:https://www.cnblogs.com/vipzhou/p/8125924.html
Copyright © 2011-2022 走看看