HTTP API认证授权方案
一.需求背景
在一些商业合作的场景下,合作方有自己的软件系统并且具备开发能力,需要访问我们的数据资源(比如:账号、产品、统计等),一般的技术方案是提供HTTP API给合作方调用。此时为了保证数据的安全性以及对数据访问范围的控制,就必须验证API调用方的身份,然后结合调用方的权限返回对应的资源,对于无法识别身份的调用方,服务端会进行拦截。
二.常用的API认证技术
2.1 App Secret Key + HMAC
这是一种用于给消息签名的技术,我们怕消息在传递的过程中被人修改,所以,我们需要用对消息进行一个MAC算法,得到一个摘要字串,然后,接收方得到消息后,进行同样的计算,然后比较这个MAC字符串,如果一致,则表明没有被修改过(整个过程参看下图)。而HMAC – Hash-based Authenticsation Code,指的是利用Hash技术完成这一工作,比如:SHA-256算法。
以SHA-256算法示例,签名流程:
- 发送方以 Key 作为算法的签名,对消息 Message 进行一个MAC算法,得到一个摘要字串 MAC。
- 接收方 接收消息 Message 后进行同样的计算得到一个摘要字串 MAC。
- 接收方 然后比较这个 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的授权许可类型,它适用于用户给第三方应用授权访问自己信息的场景。其流程图如下:
授权流程:
- 当用户 Resource Owner 访问第三方应用 Client 的时候,第三方应用会把用户带到认证服务器 Authorization Server 上去。
- 当 Authorization Server 收到这个URL请求后,其会通过 client_id 来检查 redirect_uri 和 scope 是否合法,如果合法,则弹出一个页面,让用户授权。(如果用户没有登录,则先让用户登录,登录完成后,出现授权访问页面)
- 当用户授权同意访问以后,Authorization Server 会跳转回 Client ,并以返回一个 Authorization Code。
- 接下来,Client 就可以使用 Authorization Code 获得 Access Token。
- 最后就是用 Access Token 请求 Resource Server 用户的资源。
2.2.2 Client Credential Flow
客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。
授权流程:
- Client 用自己的
client_id
和client_secret
向 Authorization Server 请求 Access Token。 - 然后 Client 使用Access Token访问 Resource Server 相关的资源。
三.业内产品调研
3.1 微信支付
微信支付采用 App Secret Key + HMAC 签名,首先介绍一下微信支付的大致原理:
微信是支付系统的开发方,掌管整个支付系统,负责记账。
商家想要接入微信支付收银,需要向微信支付部门申请商户号。
普通用户通过微信点击商家的付款链接,进行付款。
微信后台记录一笔用户和商家之间的交易流水,然后通知商家系统支付成功。
好了,现在可以知道,交易过程其实就是商家系统和微信后台的接口互相调用,而且只需要单向的关注商家调用微信后台。
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 微信公众号
-
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
接口调用请求说明
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 微信网页授权
-
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
网页授权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 客户端签名
假设传递的参数如下:
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;
}
第一步,设所有发送或者接收到的数据为集合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;
}
第二步,在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();
}
最后计算得到摘要
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 进行比较,如果一致,则说明请求没有被修改。
六.参考资料
HTTP API认证授权方案的更多相关文章
- 认证授权方案之JwtBearer认证
1.前言 回顾:认证方案之初步认识JWT 在现代Web应用程序中,即分为前端与后端两大部分.当前前后端的趋势日益剧增,前端设备(手机.平板.电脑.及其他设备)层出不穷.因此,为了方便满足前端设备与后端 ...
- 基于.NetCore3.1系列 ——认证授权方案之Swagger加锁
一.前言 在之前的使用Swagger做Api文档中,我们已经使用Swagger进行开发接口文档,以及更加方便的使用.这一转换,让更多的接口可以以通俗易懂的方式展现给开发人员.而在后续的内容中,为了对a ...
- 基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)
一.前言 回顾:基于.NetCore3.1系列 -- 认证授权方案之授权揭秘 (上篇) 在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构 ...
- OAuth2密码模式已死,最先进的Spring Cloud认证授权方案在这里
旧的Spring Security OAuth2停止维护已经有一段时间了,99%的Spring Cloud微服务项目还在使用这些旧的体系,严重青黄不接.很多同学都在寻找新的解决方案,甚至还有念念不忘密 ...
- 一看就懂的IdentityServer4认证授权设计方案
查阅了大多数相关资料,总结设计一个IdentityServer4认证授权方案,我们先看理论,后设计方案. 1.快速理解认证授权 我们先看一下网站发起QQ认证授权,授权通过后获取用户头像,昵称的流程. ...
- rest-assured之认证授权(Authentication)
rest-assured支持多种认证授权方案,比如:OAuth.digest(摘要认证).certificate(证书认证).form(表单认证)以及preemptive(抢占式基础认证)等.我们可以 ...
- asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权
开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...
- Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案
一. 前言 在上一篇文章介绍 youlai-mall 项目中,通过整合Spring Cloud Gateway.Spring Security OAuth2.JWT等技术实现了微服务下统一认证授权平台 ...
- 细说REST API安全之认证授权
认证授权包含2个方面:(1)访问某个资源时必须携带用户身份信息,如:用户登录时返回用户access_token,访问资源时携带该参数.(2)检查用户是否具备访问当前资源(url或数据)的权限:访问资源 ...
随机推荐
- CSAcademy Prefix Suffix Counting 题解
CSAcademy Prefix Suffix Counting 题解 目录 CSAcademy Prefix Suffix Counting 题解 题意 思路 做法 程序 题意 给你两个数字\(N\ ...
- LuoguP2378 因式分解II 题解
Content 输入一个多项式 \(x^2+ax+b\)(不保证 \(a,b\neq0\)),请对这个多项式进行因式分解(形式为 \((x-x_1)(x-x_2)\),其中 \(x_1>x_2\ ...
- CF805B 3-palindrome 题解
Content 给定一个整数 \(n\),请构造出长度为 \(n\) 的仅含 a.b.c 三个字母的字符串,使得其中没有长度为 \(3\) 的回文子串,并且 c 出现的次数尽可能少. 数据范围:\(1 ...
- 第二周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 ...
- AcWing 1113. 红与黑
1.题目描述 有一间长方形的房子,地上铺了红色.黑色两种颜色的正方形瓷砖. 你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动. 请写一个程序,计算你总共能够到达多少块黑色的瓷砖 ...
- 【LeetCode】657. Judge Route Circle 解题报告
[LeetCode]657. Judge Route Circle 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/judge-route- ...
- 【LeetCode】671. Second Minimum Node In a Binary Tree 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 找出所有值再求次小值 遍历时求次小值 日期 题目地址 ...
- 【LeetCode】732. My Calendar III解题报告
[LeetCode]732. My Calendar III解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/my-calendar ...
- Boosting Adversarial Training with Hypersphere Embedding
目录 概 主要内容 代码 Pang T., Yang X., Dong Y., Xu K., Su H., Zhu J. Boosting Adversarial Training with Hype ...
- A pure L1-norm principal component analysis
@ 目录 问题 细节 的损失函数 算法 投影 坐标系 载荷向量 A pure L1-norm principal component analysis 虽然没有完全弄清楚其中的数学内涵,但是觉得有趣, ...