一.需求背景

在一些商业合作的场景下,合作方有自己的软件系统并且具备开发能力,需要访问我们的数据资源(比如:账号、产品、统计等),一般的技术方案是提供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. 针对HttpClient 重写 HttpRequestRetryHandler针对特定异常 增加重试

    调用方法: public static String doGet(String url) { try { RequestConfig defaultRequestConfig = RequestCon ...

  2. 关于后端 Entity Model Domain 的分界线

    前言:在我们开发中经常用一种类型的值来接收来自Dao层的数据并将它传送给前端,或者作为逻辑处理,一般这种类型有三种 Entity  Model  Domain  我们该如何准确的应用这三种类型呢?这三 ...

  3. 系统分析师教程(张友生)高清pdf下载

    最近准备考系统分析师,故找了一本张又生编著的<系统分析师教程>的电子书,本来想买本书,可惜有点小贵,舍不得,故寻找电子版下载,花了不少时间才找到,现在分享给大家. http://item. ...

  4. JAVA地址通过百度地图API转化为经纬度

    public static Map getLngAndLat(String address) { Map map = new HashMap(); String url = "http:// ...

  5. Windows系统安装Redis服务

    下载压缩包,登录 https://github.com/MicrosoftArchive/redis/releases  下载Redis-x64-3.0.504.zip 我也上传了一份  https: ...

  6. 【剑指Offer】数组中出现次数超过一半的数字 解题报告(Python)

    [剑指Offer]数组中出现次数超过一半的数字 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-inter ...

  7. hdu-1421搬寝室(dp)

    http://acm.hdu.edu.cn/showproblem.php?pid=1421; 思路:先将所给的椅子的价值按升序排列,举个例子,四张椅子的价值分别为a,b,c,d(a<b< ...

  8. The Balance(poj2142)

    The Balance Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5452   Accepted: 2380 Descr ...

  9. contrastive CAM

    目录 概 主要内容 一个有趣的应用 > Prabhushankar M., Kwon G., Temel D. and AlRegib G. Contrastive explanation in ...

  10. SROP

    先放个例题吧,原理后面有时间再更:BUUCTF ciscn_2019_s_3 保护只开了nx 1 signed __int64 vuln() 2 { 3 signed __int64 v0; // r ...