OAuth2.0原理图解:第三方网站为什么可以使用微信登录

7,944 阅读7分钟

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习


1 文章概述

假设小明开发了一个A网站,需要支持微信登陆和淘宝账号登陆。如果你是微信或者淘宝开发人员,你会怎么设计这个功能?本文结合淘宝开放平台官方文档以淘宝账号为例。

从最简单视角去思考,用户在网站A输入淘宝用户名和密码,网站A调用淘宝接口校验输入信息,校验通过则登陆成功,整体流程如下图:


01 第三方登陆简单思路.jpg


上述思路存在什么问题?最显著问题就是信息安全问题。问题第一个方面是用户需要将淘宝用户名和密码输入网站A,这样会带来用户名和密码泄露风险。问题第二个方面是如果用户不信任网站A,那么也不会输入淘宝用户名和密码,影响网站A业务开展。


2 OAuth2.0

第三方登陆信息安全问题应该如何解决?OAuth是一种流行标准。如果执行这行这个标准,那么用户可以在不告知A网站淘宝用户名和密码情况下,使用淘宝账号登陆A网站。

目前已经发展到OAuth2.0版本,相较于1.0版本更加关注客户端开发者简易性,而且为桌面应用、web应用、手机设备提供专门认证流程。


2.1 四种角色

OAuth2.0标准定义四种角色:

  • 客户端(Client)
  • 资源所有者(Resource Owner)
  • 资源服务器(Resource Server)
  • 授权服务器(Authorization Server)

四种角色交互流程:

02 OAuth2_四种角色_01.jpg

本文场景对应四种角色:

02 OAuth2_四种角色_02.jpg


2.2 四种模式

OAuth2.0标准定义四种授权模式:

  • 授权码模式(authorization code)
  • 隐式模式(implicit)
  • 密码模式(password)
  • 客户端模式(client credentials)

四种授权模式中最常用的是授权码模式,例如微信开发平台文档介绍对于网站应用微信OAuth2.0授权登录目前支持授权码模式,所以本文只介绍授权码模式,后续文章会详细比较四种模式。


2.3 整体流程

第一个流程是创建应用,A网站开发者首先去淘宝开放平台创建应用,开放平台会生成一个client_id作为A网站唯一标识。

第二个流程是授权流程,用户在A网站点击使用淘宝账号登陆时,实际上跳转至A网站拼接授权URL页面,这个页面由淘宝提供。用户在授权页面输入淘宝用户名和密码,校验成功后跳转至A网站回调地址,这时A网站会拿到一个code,后台再使用code去获取access_token。

第三个流程是获取信息,获取到access_token相当于获取到一把钥匙,再按照规范调用淘宝对外提供接口就可以获取到用户数据。


03 OAuth2_整体流程.jpg


2.4 为什么安全

第一个方面A网站开发人员需要在淘宝开放平台进行申请,需要输入个人信息或者公司信息,这样A网站可靠性有了一定程度保证。

第二个方面在第一章节方案用户需要在A网站输入淘宝用户名和密码,但是在OAuth2.0方案2.4步骤虽然也要输入淘宝用户名密码,但是这个页面由淘宝官方提供,安全性得到了保证。

第三个方面access_token(令牌)并没有在浏览器中传递,而是需要A网站在获取到code之后去后台程序换取,避免了钥匙泄露风险。

第四个方面code(授权码)在浏览器传递有一定风险,但是具有两个特性一定程度保证了安全:

code具有效期,超期未使用需要重新按授权流程获取
code只能使用一次,使用后需要重新按授权流程获取

3 JWT

3.1 发现问题

在第二章节详细分析了OAuth2.0协议,在实现流程章节分析了创建应用、授权流程、获取信息三个流程,我们发现一个问题:在流程图3.3步骤资源服务器需要远程调用授权服务器check_token端点校验令牌是否有效,这样比较消耗性能。

如果资源服务器和授权服务器约定一个密钥对,授权服务器用秘钥加密令牌,当资源服务器接收到令牌时进行解密直接对令牌进行校验,这样可以节省远程交互。


3.2 进行比较

JSON Web Token(JWT)可以解决上述问题,作为一个开放标准(RFC 7519)定义了一种紧凑的自包含方式,用于作为JSON对象在各方之间安全地传输信息。


3.2.1 普通令牌

{
    "access_token": "1fa67a8b-12a9-4862-b9ef-983d6554cd19",
    "token_type": "bearer",
    "refresh_token": "e0a417a9-33cc-4034-84c1-d09dc80a8c31",
    "expires_in": 49999,
    "scope": "test"
}

3.2.2 JWT

{
    "access_token": "aaa.bbb.ccc",
    "token_type": "bearer",
    "refresh_token": "ddd.eee.fff",
    "expires_in": 49999,
    "scope": "test"
}

JWT分为头部、有效载荷和签名三个部分。头部包含签名算法以及token类型。有效载荷包含真正业务信息,例如用户ID、姓名、邮箱、权限信息。头部和有效载荷任何人都可以读出来,所以需要用签名防止篡改:头部和有效载荷分别进行Base64编码,编码后用 . 连接组成新字符串,最后使用头部声明算法进行签名。

JWT = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)

JWT令牌内容:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbeyJuYW1lIjoidGVzdF9uYW1lIn0seyJlbWFpbCI6InRlc3RfZW1haWwifV0sImlhdCI6MTY0NzE4MTc0NywiZXhwIjoxNjQ3MzU5OTk5LCJhdWQiOiJ0ZXN0MiIsImlzcyI6InRlc3QxIiwic3ViIjoidGVzdDMifQ.k0dknW7ff4KyMIMbBmnZtI3nf2lWN9KVKQrGwcyOGYI

JWT解码内容:

{
    "header":{
        "alg":"HS256",
        "typ":"JWT"
    },
    "payload":{
        "data":[
            {
                "name":"test_name"
            },
            {
                "email":"test_email"
            }
        ],
        "iat":1647181747,  // Issued At 签发时间
        "exp":1647359999,  // Expiration 过期时间
        "aud":"test2",     // Audience 受众
        "iss":"test1",     // Issuer 签发人
        "sub":"test3"      // Subject 主题
    }
}

3.3 整体流程

03 OAuth2_整体流程_JWT.jpg


4 OpenID Connect

4.1 授权与认证

在第二章节详细分析了OAuth2.0协议,在实现流程章节分析了创建应用、授权流程、获取信息三个流程,我们发现一个问题:客户端在获取到令牌之后,还需要调用资源服务器接口获取用户信息,有没有一种协议可以在返回令牌时同时将用户是谁返回呢?回答这个问题之前首先对比一组概念:授权与认证。

授权关注通信实体具有什么权限,认证关注通信实体是谁。OAuth2.0只有授权流程,返回令牌之后授权流程已经完成,OpenID connect在此基础上进行了扩展,客户端能够通过认证来识别用户。


4.2 三种角色

OpenID Connect协议定义三种角色:

  • 最终用户(End User)
  • 依赖方(Relying Party)
  • 身份认证提供商(Identity Provider)

三种角色交互流程:

04 OIDC_三种角色_01.jpg

本文场景对应三种角色:

04 OIDC_三种角色_02.jpg


4.3 进行比较

4.3.1 id_token

OIDC标准协议新增id_token字段,这个字段符合JWT标准格式,那么为什么不与第三章节采用相同方式,在access_token包含有效负载信息而是新增id_token字段?

因为即使access_token可以加入用户信息并且通过加签防篡改,但是用户每次请求都需要携带access_token,增加了带宽和信息泄露风险。

{
    "access_token": "1fa67a8b-12a9-4862-b9ef-983d6554cd19",
    "token_type": "bearer",
    "refresh_token": "e0a417a9-33cc-4034-84c1-d09dc80a8c31",
    "expires_in":3599,
    "id_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbeyJuYW1lIjoidGVzdF9uYW1lIn0seyJlbWFpbCI6InRlc3RfZW1haWwifV0sImlhdCI6MTY0NzE4MTc0NywiZXhwIjoxNjQ3MzU5OTk5LCJhdWQiOiJ0ZXN0MiIsImlzcyI6InRlc3QxIiwic3ViIjoidGVzdDMifQ.k0dknW7ff4KyMIMbBmnZtI3nf2lWN9KVKQrGwcyOGYI"
}

4.3.2 userinfo

OIDC标准协议要求提供了一个/userinfo端点,可以通过传入access_token调用获取用户信息,那么既然id_token已经包含了用户信息,为什么还要提供这个端点?

这是因为id_token提供用户基本信息,如果客户端只要求获取基本信息,那么无需调用/userinfo端点,但是如果需要获取用户详细信息,那么需要调用/userinfo端点进行查询。


4.4 整体流程

05 OIDC_整体流程.jpg


5 相关文档

JWT在线编码工具

JWT在线解码工具

淘宝开放平台用户授权介绍

网站应用微信登录开发指南


欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习