卖烤红薯也要懂OAuth2.0
题图 | @peixi
小季临近毕业了,但是他听说最近经济形势不太好,消费降级成为了一个热议的话题。和他最直接相关的就是各大互联网公司缩招了,本身就技术平平的他,这下更为找工作发愁了。
小季刚出校园,对经济理论这些不甚了解,这一时半会也找不到互联网开发相关的岗位,竟有点束手无策了。不过他对信息还是有一定的敏感度,由于大家现在普遍比较穷,很多人不得消费低价的商品。
所以网络上出现了一句:“吃着涪陵榨菜,喝着二锅头,出门共享单车,购物拼夕夕。”小季心想,做事情还是得顺势而为,不要老想着逆天。俗话说:上大学不如卖红薯。烤红薯作为典型的快消品,这是妥妥的商机啊。
几年过去了,小季变成了中等季。而他的「初恋红薯」也已经在全世界开了100000家连锁,slogan就是「捧在手心,化在口中」。生意做这么大,当然是有独家研制的配方,传闻是这个配方是小季用机器学习训练出来的。
那么问题来了,如何保证独家配方能安全的到达每个门店?小季基于以前学过的OAuth2.0,设计了一个鉴权的流程。
由于全世界门店太多,我们现在和一些第三方的物流中心合作。这些物流中心必须是在「初恋红薯」注册审核过的,才会拥有配送的资格。
上海这一片的配送服务是由一家名为「字节物流」的公司承包的(公众号「字节流」旗下物流公司)。下面我们将角色捋一捋。
门店 = 用户;
秘制原料 = 用户数据;
物流公司 = 第三方;
总部 = 数据拥有方
现在上海一家门店S需要通过「字节物流」Z向总部拿原料。
如果是最传统的方式,S把自己的账号密码交给Z,Z把S的账号密码带到总部请求验证。这样就会产生一个问题:假如Z保存了S的账号密码,在S不知情的情况下去骗取原料呢?而S要频繁地修改密码才能防止这个行为,这很麻烦,且不安全。
现在小季基于OAuth2.0设计的方式是这样的:
完成服务后,只要access_token失效,第三方则无法访问数据中心。
那么,到底什么是OAuth?
维基百科给出了这样的解释:
开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。
在越来越开放的互联网世界,这是一种常用的授权方式,有效地连接和隔离了系统。
比如我们平时在使用第三方APP或第三方网站时,常常可以使用微信登录,这是微信开放平台给予了第三方的开发者权限,微信的登录权限也是基于OAuth2.0的。下面我们通过一个demo,来了解一下微信登录的流程,可以结合对比「初恋红薯」的例子。
现在我们有一个自己开发的网站,为了给予用户更好的体验,允许用户使用微信登录,而不需要立刻注册账号。
首先我们需要请求微信的授权:
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
我对其中的几个重要参数具体解释一下:
appid=APPID
,APPID是第三方开发者在微信开放平台上注册应用ID
redirect_uri=REDIRECT_URI
,若用户同意授权,则回调REDIRECT_URI这个接口,这个接口是开发者部署在自己的服务器上的,用于接受返回的code,并利用code做进一步处理。
response_type
、 scope
、 state
是微信规定的返回类型、权限域、状态,代表了需要请求的资源的种类、适用范围等。具体可以参考微信开放平台的官方文档。
上述参数中,最需要开发者关注的是回调接口,这个需要我们自己编写,以继续后续的操作。下面我用一段简易的Java代码来演示:
// 对外接口
@RequestMapping(path = "/user", method = RequestMethod.GET)
public ResponseEntity<?> wxcall(@RequestParam(value = "code", required = false) String code,
HttpServletResponse response) {
User user = weixinService.getWxUserInfo(code);
}
// 根据code获取access_token 和 openid
public User getWxUserInfo(String code) {
JsonParser parser = new JsonParser();
String uri = UriComponentsBuilder
.fromHttpUrl("https://api.weixin.qq.com/sns/oauth2/access_token")
.queryParam("appid", WeixinConstants.APPID )
.queryParam("secret", WeixinConstants.APPSECRET)
.queryParam("code", code)
.queryParam("grant_type", "authorization_code")
.toUriString();
HttpEntity<String> response = templateProvider.getRestTemplate()
.exchange(uri, HttpMethod.GET, null, String.class);
JsonObject accessTokenJson = null;
try {
accessTokenJson = parser.parse(response.getBody()).getAsJsonObject();
} catch (Exception e) {
e.printStackTrace();
}
if (accessTokenJson == null || !accessTokenJson.has("access_token")) {
logger.error("fetch accesstoken error {}", response.getBody());
return null;
}
// 获取 用户信息
String infoUri = UriComponentsBuilder
.fromHttpUrl("https://api.weixin.qq.com/sns/userinfo")
.queryParam("access_token", accessTokenJson.get("access_token").getAsString())
.queryParam("openid", accessTokenJson.get("openid").getAsString())
.queryParam("lang", "zh_CN")
.toUriString();
HttpEntity<String> userInfo = templateProvider.getRestTemplate()
.exchange(infoUri, HttpMethod.GET, null, String.class);
JsonObject userInfoJson = null;
try {
userInfoJson = parser.parse(userInfo.getBody()).getAsJsonObject();
} catch (Exception e) {
e.printStackTrace();
}
if (userInfoJson == null ||
(userInfoJson.has("errcode") && userInfoJson.get("errcode").getAsInt() != 0)) {
logger.error("fetch userinfo error {}", userInfo.getBody());
return null;
}
此时,用户的基本信息已经封装在了userInfoJson中,可以享用了。
这样一来,「初恋红薯」的秘制原料就能在可控安全的范围内运输使用了。小季步入中年,看着自己的连锁店在神州大地上星罗棋布,不禁露出了优雅的成功中年男人の微笑。
突然,室友摇醒了梦中的小季。“快醒醒!你的邮箱里收到offer啦!”小季这才惊觉原来是南柯一梦,打开邮箱,邮件上赫然写着:月薪4k包吃住。
——————
如今基于微信的公众号、小程序开发如火如荼,如果你想要尝试,可以先购置一台自己的云主机,每月仅需8元,另外添加微信,我会返还优惠红包,我觉得初学者都需要一台云主机学习实践。推荐阅读:
为什么说初学者都需要一台云服务器
购买链接(可直接点击左下"阅读原文"):
https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.e5hos4hs