一.需求背景

在一些商业合作的场景下,合作方有自己的软件系统并且具备开发能力,需要访问我们的数据资源(比如:账号、产品、统计等),一般的技术方案是提供HTTP API给合作方调用。此时为了保证数据的安全性以及对数据访问范围的控制,就必须验证API调用方的身份,然后结合调用方的权限返回对应的资源,对于无法识别身份的调用方,服务端会进行拦截。

二.常用的API认证技术

2.1 App Secret Key + HMAC

这是一种用于给消息签名的技术,我们怕消息在传递的过程中被人修改,所以,我们需要用对消息进行一个MAC算法,得到一个摘要字串,然后,接收方得到消息后,进行同样的计算,然后比较这个MAC字符串,如果一致,则表明没有被修改过(整个过程参看下图)。而HMAC – Hash-based Authenticsation Code,指的是利用Hash技术完成这一工作,比如:SHA-256算法。

以SHA-256算法示例,签名流程:

  1. 发送方以 Key 作为算法的签名,对消息 Message 进行一个MAC算法,得到一个摘要字串 MAC
  2. 接收方 接收消息 Message 后进行同样的计算得到一个摘要字串 MAC
  3. 接收方 然后比较这个 MAC 字符串是否一致,如果一致,则表明没有被修改过。

2.2 OAuth 2.0

OAuth 是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。OAuth 2.0依赖于TLS/SSL的链路加密技术(HTTPS),完全放弃了签名的方式,认证服务器再也不返回什么token secret的密钥了。

2.2.1 Authorization Code Flow

Authorization Code 是最常使用的OAuth 2.0的授权许可类型,它适用于用户给第三方应用授权访问自己信息的场景。其流程图如下:

授权流程:

  1. 当用户 Resource Owner 访问第三方应用 Client 的时候,第三方应用会把用户带到认证服务器 Authorization Server 上去。
  2. Authorization Server 收到这个URL请求后,其会通过 client_id 来检查 redirect_uri 和 scope 是否合法,如果合法,则弹出一个页面,让用户授权。(如果用户没有登录,则先让用户登录,登录完成后,出现授权访问页面)
  3. 当用户授权同意访问以后,Authorization Server 会跳转回 Client ,并以返回一个 Authorization Code。
  4. 接下来,Client 就可以使用 Authorization Code 获得 Access Token。
  5. 最后就是用 Access Token 请求 Resource Server 用户的资源。

2.2.2 Client Credential Flow

客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

授权流程:

  1. Client 用自己的 client_idclient_secretAuthorization Server 请求 Access Token。
  2. 然后 Client 使用Access Token访问 Resource Server 相关的资源。

三.业内产品调研

3.1 微信支付

  1. 微信支付采用 App Secret Key + HMAC 签名,首先介绍一下微信支付的大致原理:

    • 微信是支付系统的开发方,掌管整个支付系统,负责记账。

    • 商家想要接入微信支付收银,需要向微信支付部门申请商户号。

    • 普通用户通过微信点击商家的付款链接,进行付款。

    • 微信后台记录一笔用户和商家之间的交易流水,然后通知商家系统支付成功。

    好了,现在可以知道,交易过程其实就是商家系统和微信后台的接口互相调用,而且只需要单向的关注商家调用微信后台。

  2. JSAPI支付-开发文档,签名算法:

    假设传递的参数如下:

    appid: wxd930ea5d5a258f4f
    mch_id: 10000100
    device_info: 1000
    body: test
    nonce_str: ibuaiVcKdpRxkhJA

    第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";

    第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

    stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key
    sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式
    sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式,部分语言的hmac方法生成结果二进制结果,需要调对应函数转化为十六进制字符串。

    最终发送的数据:

    <xml>
    <appid>wxd930ea5d5a258f4f</appid>
    <mch_id>10000100</mch_id>
    <device_info>1000</device_info>
    <body>test</body>
    <nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
    <sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
    </xml>

3.2 微信公众号

  1. 微信公众号-获取AccessToken 开发文档

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

  2. 接口调用请求说明

    GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

    参数说明

    • grant_type:获取access_token填写client_credential
    • appid:第三方用户唯一凭证
    • secret:第三方用户唯一凭证密钥,即appsecret

    返回情况

    正常情况下,微信会返回下述JSON数据包给公众号:

    {"access_token":"ACCESS_TOKEN","expires_in":7200}

    参数说明

    • access_token:获取到的凭证
    • expires_in:凭证有效时间,单位:秒

3.3 微信网页授权

  1. 微信网页授权-开放文档

    如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

  2. 网页授权AccessToken的流程

    • 第一步:引导用户进入授权页面同意授权,获取code
    • 第二步:通过code换取网页授权access_token
    • 第三步:如果需要,开发者可以刷新网页授权access_token,避免过期。
    • 第四步:通过网页授权access_token和openid获取用户基本信息

四、如何选择HTTP API鉴权方案

4.1 HTTP API鉴权方式的对比

前面介绍几种常用的API鉴权技术,在产品调研环节分别可以找到其落地场景。

首先拿 微信支付 来看,一笔交易的下单在一个接口中完成,请求的参数包含金额、商户号等,都是非常关键的参数,必须要求严格校验,防止被攻击篡改,同时参数还有时效限制(含时间戳)。此时自然不适合使用OAuth 2.0的鉴权方式,AccessToken的请求不对参数进行校验。

然后看下 微信公众号 AccessToken的场景,可以看到使用AccessToken调用接口(管理公众号菜单、管理账号)都属于一个企业范围内的数据,可以这么理解,这部分信息属于微信授权给企业的一份独立资产,公众号对应的企业有权限管理这份资产。此时使用AccessToken可以很好的控制访问范围。这里不是不能用 App Secret Key + HMAC 的鉴权方式,而是觉得这部分信息安全要求没有支付高。另一方面,不对参数加密,通信也会更加高效(加密有耗时,比如文件上传也不太适合进行加密)。

最后看下 微信网页授权,同理类推,用户的信息属于每个独立的用户,获取的AccessToken的访问范围也只能是当前用户的信息。

4.2 HTTP API鉴权经验分享

上面提到的两种鉴权方式,无论是作为服务方还是调用方,我都在工作中都有使用到。个人觉得 App Secret Key + HMAC 实践起来相对容易,客户端对服务端的调用比较直接,鉴权不通过时可以通过接口的响应及时获得反馈。

另一种,OAuth 2.0的AccessToken的方式,服务端需要维护AccessToken,并且还要控制AccessToken的失效,拿微信公众号来看,新的AccessToken生成后,旧的AccessToken在5分钟之内有效;客户端需要维护一份AccessToken并及时刷新保持有效。再看下业务的交互上,比起 App Secret Key + HMAC 明显多一些环节,环节多了就容易犯错。

4.3 结论

最后,具体选择使用哪一种鉴权方式,我想还是需要结合对应的业务场景来看。比如业务发展的初期,需要快速开发推向市场,这时就没必要纠结,直接选择一种相对而言简单且不容易犯错的 App Secret Key + HMAC 签名鉴权。等到后续用户量大了,业务成熟了,可以参考 微信公众号、AWS s4签名,精细划分每一个AccessToken的访问范围。

五.实践-方案实现

实践案例使用 App Secret Key + HMAC 的鉴权方式,下面会详细介绍 客户端签名服务端验签 的过程。

5.1 分配AppId和AppSecret

在签名之前首先需要分配 AppId 和 AppSecret,落实到业务场景中,这个就是我们作为资源方分配给合作方的租户配置。关于 AppId 和 AppSecret 的生成没有标准规范,每家的生成算法都不一样,也都不会公布出来。本次案例,我们使用32位的uuid作为AppId,以64位的hash串作为AppSecret:

// 生成AppId
private static String generateAppId() {
UUID uuid = UUID.randomUUID();
return uuid.toString().replaceAll("-", "");
}
// 生成AppSecret
private static String generateAppSecret() {
UUID uuid = UUID.randomUUID();
return DigestUtils.sha256Hex(uuid.toString());
}

计算得出:

APPID = "ivv49q404zfp8075ivbcwye4ardqafha"

APP_SECRET = "ut338c829x2yzfnklvy8lezyu3ndsss68dyzo9opt3icbin7lv7p2j4b0i2cvjz8"

5.2 客户端签名

  1. 假设传递的参数如下:

    private static final String APPID = "ivv49q404zfp8075ivbcwye4ardqafha";
    
    /**
    * 下单请求对象
    */
    class PlaceOrderForm {
    String appid;
    Integer totalAmount;
    String body;
    String detail;
    String nonceStr;
    } /**
    * 模拟请求对象
    */
    private static PlaceOrderForm mockWebForm () {
    PlaceOrderForm form = new PlaceOrderForm();
    form.appid = APPID;
    form.body = "test";
    form.detail = "test";
    form.nonceStr = "123456";
    form.totalAmount = 88;
    return form;
    }
  2. 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    /**
    * TreeMap会根据Key排序
    */
    private static Map<String, String> confirmToMap(PlaceOrderForm form) throws Exception {
    Map<String, String> map = new TreeMap<>();
    Field[] fields = PlaceOrderForm.class.getDeclaredFields();
    for (Field field : fields) {
    field.setAccessible(true);
    Object value = field.get(form);
    if (value != null && !field.getName().equals("sign")) {
    if (value instanceof String) {
    map.put(field.getName(), (String) value);
    } else if (value instanceof Integer) {
    map.put(field.getName(), String.valueOf(value));
    }
    }
    }
    return map;
    }
  3. 第二步,在stringA最后拼接上appsecret得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

    private static final String APP_SECRET = "ut338c829x2yzfnklvy8lezyu3ndsss68dyzo9opt3icbin7lv7p2j4b0i2cvjz8";
    
    /**
    * 生成签名
    */
    private static String sign(Map<String, String> params) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, String> entry : params.entrySet()) {
    sb.append(entry.getKey());
    sb.append("=");
    sb.append(entry.getValue());
    sb.append("&");
    }
    sb.append("appsecret=");
    sb.append(APP_SECRET);
    return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }
  4. 最后计算得到摘要

    public static void main(String[] args) {
    PlaceOrderForm form = mockWebForm();
    try {
    Map<String, String> stringStringMap = confirmToMap(form);
    String sign = sign(stringStringMap);
    System.out.println(sign);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

5.3 服务端验签

服务端接收请求的参数,使用同样的签名算法计算出摘要 sign 进行比较,如果一致,则说明请求没有被修改。

六.参考资料

  1. HTTP API 认证授权术 || 酷壳 - CoolShell

  2. Signature Version 4 规范请求 - AWS General Reference

  3. go语言并发编程与Context

HTTP API认证授权方案的更多相关文章

  1. 认证授权方案之JwtBearer认证

    1.前言 回顾:认证方案之初步认识JWT 在现代Web应用程序中,即分为前端与后端两大部分.当前前后端的趋势日益剧增,前端设备(手机.平板.电脑.及其他设备)层出不穷.因此,为了方便满足前端设备与后端 ...

  2. 基于.NetCore3.1系列 ——认证授权方案之Swagger加锁

    一.前言 在之前的使用Swagger做Api文档中,我们已经使用Swagger进行开发接口文档,以及更加方便的使用.这一转换,让更多的接口可以以通俗易懂的方式展现给开发人员.而在后续的内容中,为了对a ...

  3. 基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)

    一.前言 回顾:基于.NetCore3.1系列 -- 认证授权方案之授权揭秘 (上篇) 在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构 ...

  4. OAuth2密码模式已死,最先进的Spring Cloud认证授权方案在这里

    旧的Spring Security OAuth2停止维护已经有一段时间了,99%的Spring Cloud微服务项目还在使用这些旧的体系,严重青黄不接.很多同学都在寻找新的解决方案,甚至还有念念不忘密 ...

  5. 一看就懂的IdentityServer4认证授权设计方案

    查阅了大多数相关资料,总结设计一个IdentityServer4认证授权方案,我们先看理论,后设计方案. 1.快速理解认证授权 我们先看一下网站发起QQ认证授权,授权通过后获取用户头像,昵称的流程. ...

  6. rest-assured之认证授权(Authentication)

    rest-assured支持多种认证授权方案,比如:OAuth.digest(摘要认证).certificate(证书认证).form(表单认证)以及preemptive(抢占式基础认证)等.我们可以 ...

  7. asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权

    开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...

  8. Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案

    一. 前言 在上一篇文章介绍 youlai-mall 项目中,通过整合Spring Cloud Gateway.Spring Security OAuth2.JWT等技术实现了微服务下统一认证授权平台 ...

  9. 细说REST API安全之认证授权

    认证授权包含2个方面:(1)访问某个资源时必须携带用户身份信息,如:用户登录时返回用户access_token,访问资源时携带该参数.(2)检查用户是否具备访问当前资源(url或数据)的权限:访问资源 ...

随机推荐

  1. CSAcademy Prefix Suffix Counting 题解

    CSAcademy Prefix Suffix Counting 题解 目录 CSAcademy Prefix Suffix Counting 题解 题意 思路 做法 程序 题意 给你两个数字\(N\ ...

  2. LuoguP2378 因式分解II 题解

    Content 输入一个多项式 \(x^2+ax+b\)(不保证 \(a,b\neq0\)),请对这个多项式进行因式分解(形式为 \((x-x_1)(x-x_2)\),其中 \(x_1>x_2\ ...

  3. CF805B 3-palindrome 题解

    Content 给定一个整数 \(n\),请构造出长度为 \(n\) 的仅含 a.b.c 三个字母的字符串,使得其中没有长度为 \(3\) 的回文子串,并且 c 出现的次数尽可能少. 数据范围:\(1 ...

  4. 第二周Python笔记 数据类型 列表 字典

    列表,拉锁式儿合并. [ [a,b] for a,b in zip(list1,list2)] #最笨的 a=[1,2,3,4,5] b=[2,3,4,5,6] d=[] for i in range ...

  5. AcWing 1113. 红与黑

    1.题目描述 有一间长方形的房子,地上铺了红色.黑色两种颜色的正方形瓷砖. 你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动. 请写一个程序,计算你总共能够到达多少块黑色的瓷砖 ...

  6. 【LeetCode】657. Judge Route Circle 解题报告

    [LeetCode]657. Judge Route Circle 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/judge-route- ...

  7. 【LeetCode】671. Second Minimum Node In a Binary Tree 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 找出所有值再求次小值 遍历时求次小值 日期 题目地址 ...

  8. 【LeetCode】732. My Calendar III解题报告

    [LeetCode]732. My Calendar III解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/my-calendar ...

  9. Boosting Adversarial Training with Hypersphere Embedding

    目录 概 主要内容 代码 Pang T., Yang X., Dong Y., Xu K., Su H., Zhu J. Boosting Adversarial Training with Hype ...

  10. A pure L1-norm principal component analysis

    @ 目录 问题 细节 的损失函数 算法 投影 坐标系 载荷向量 A pure L1-norm principal component analysis 虽然没有完全弄清楚其中的数学内涵,但是觉得有趣, ...