OAuth2.0授权

最近一直在负责开发公司的开放平台相关工作,对接淘宝,阿里巴巴等开放平台,同时也负责开发系统的开放平台,在此稍作总结。本文只稍微分析聊一下授权码模式,并且不尝试解释OAuth2.0参数为什么不是驼峰的……

参考资料


RFC6794
理解OAuth 2.0

使用场景


用户登录云管店应用,此时没有办法直接登录阿里巴巴应用查看数据,或者阿里巴巴数据还未经过处理,不是用户的目标数据。

用户登录云管店(假设该应用对接了阿里巴巴应用的接口)应用,查看自己门店当前的库存数量,同时为了更直观的了解到当前阿里巴巴上挂的店铺的库存,云管店要去访问阿里巴巴接口拉取到该用户在阿里巴巴的店铺的仓库数量,统计成报表。

如果不适用OAuth2.0云管店应该如何读取到阿里巴巴上的库存数量?

image.png

用户提供阿里巴巴账号密码给云管店云管店通过账号密码即可读取到库存信息。那么这么做有带来什么隐患?

  • 阿里巴巴账号密码泄露给云管店云管店可以任意获取用户在阿里巴巴上的数据
  • 云管店数据库如果泄露,也把阿里巴巴的账号密码等数据泄露出去
  • 为了防止云管店任意读取数据,只能通过修改账号密码
  • ...

基于数据开放,且为了保护用户数据安全等诸多问题,OAuth2.0应运而生,并成为当前最主流的解决方案。

OAuth2.0 解决方案


OAuth2.0客户端服务提供商之间,设置了一个授权访问的屏障。客户端无法直接拿到服务提供商的登录账号密码,也就无法直接登录服务提供商,只能请求授权服务提供商

此时会要求用户登录资源提供商(该登录服务由服务提供商提供,不会存在账号密码泄露等问题)。登录后,授权服务提供商提示用户确认授权后提供给客户端一个token令牌。服务提供商根据令牌的时效和授权范围,向客户端开放数据。

image.png

OAuth2.0客户端授权模式


  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

授权码模式


授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。(本文只提到授权码模式,其他相关客户端授权模式请参考上文的参考资料进行了解)

image.png

流程解析

(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

A步骤中,客户端申请认证的URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为"code"
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

C步骤中,服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项。

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

基于规范,动手实现一个简易版的授权码模式


对应于A步骤,客户端发起授权请求(该请求可以要求登录,用户访问该请求需要登录)。授权参数需要参照OAuth2.0规范,最好是相应的参数名称都按照规范来。

@RequestMapping(value = "/authorize")
public String authorize(ModelMap modelMap, AuthorizeDTO authorizeDTO) {
        
        // 如果是授权码模式
        if(GrantTypeEnum.AUTHORIZATION_CODE.getValue().equals(authorizeDTO.getResponse_type())) {
            // 检验客户信息
            if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) {
                ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID);
                return returnErrorPage();
            }
            // 检验重定向地址
            if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeDTO.getClient_id(), authorizeDTO.getRedirect_uri())) {
                ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_REDIRECT_URI);
                return returnErrorPage();
            }
            
            modelMap.put("client_id", authorizeDTO.getClient_id());
            modelMap.put("redirect_uri", authorizeDTO.getRedirect_uri());
            modelMap.put("state", authorizeDTO.getState());
        }
        
        return "/auth";
    }

对应步骤C,确认授权后可以获取到相应的code与state等参数,附着在回调地址中,且该回调地址必须与申请资质时填写的回调的地址(申请资质需要客户端应用向服务提供商申请,由服务提供商颁发相应的key与secret)

@RequestMapping(value = "/confirm")
public String accessConfirm(ModelMap modelMap, AuthorizeDTO authorizeDTO) {
        
        // 检验客户信息
        if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) {
            ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID);
            return returnErrorPage();
        }
        // 检验重定向地址
        if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeDTO.getClient_id(), authorizeDTO.getRedirect_uri())) {
            ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_REDIRECT_URI);
            return returnErrorPage();
        }
        
        // 根据填写的回调地址回调回去
        return "redirect:" + authorizeDTO.getRedirect_uri()+"?code="+StoreFactory.getCodeStore().createUUIDCode(authorizeDTO.getClient_id())
            +"&state="+authorizeDTO.getState();
    }

对应步骤E,使用获取到的code去换取token,或者使用旧的refresh_token去获取新的token

@RequestMapping(value = "/token")
@ResponseBody
public ResultObject accessToken(ModelMap modelMap, AuthorizeTokenDTO authorizeTokenDTO) {
        
        // 检验客户信息
        if(!StoreFactory.getClientStore().isConatinsClient(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getClient_secret())) {
            return ResultMessage.ERROR_CLIENT_ID.getResultObject();
        }
        
        // 检验重定向地址
        if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getRedirect_uri())) {
            return ResultMessage.ERROR_REDIRECT_URI.getResultObject();
        }
        
        // 检验code
        if(!StoreFactory.getCodeStore().isRightCode(authorizeTokenDTO.getCode(), authorizeTokenDTO.getClient_id())) {
            return ResultMessage.ERROR_CODE.getResultObject();
        }
        
        // 生成token
        if(GrantTypeEnum.AUTHORIZATION_CODE.equals(authorizeTokenDTO.getGrant_type())) {
            // 也可以根据redirect_uri 回调回去
            // 也可以将返回值包装成Josn返回
            // 
            return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id()));
            
        }
        
        // 刷新token
        if(GrantTypeEnum.REFRESH_TOKEN.equals(authorizeTokenDTO.getGrant_type())) {
            // 拿到refreshToken 并检验刷新
            // 这里没有做实现,但是原理一致
            return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id()));
        }
        
        return ResultMessage.ERROR_GRANT_TYPE.getResultObject();
    }

如此简单便可以实现一个最简易的授权码模式的服务。麻雀虽小,却也五脏俱全,不能直接用于真实生产环境,但是对于理解OAuth2.0的授权过程却也足以。

代码地址https://gitee.com/linweifeng/OAuth/tree/master

分布式环境

如果是单机应用,我们的授权服务,资源服务(开放的接口)都是可以统一放在一个应用上,那么实现自然是非常简单,通过拦截器/自定义注解实现AOP都可以做到非常完美,代码写起来也很6很顺手。

但是如果是分布式环境,比如现在最流行的微服务架构就需要考虑的问题比较多,比如token校验合法性。

image.png

授权服务独立一个应用,功能简单,轻量.
资源服务可能由于访问量较大,需要部署多台服务,通过负载均衡来保证服务稳定。

当客户端授权完成并成功拿到token之后即可用它来访问资源服务,拉取数据。那么此时就需要校验token的合法性,那么谁来校验token才是最合适的呢?

资源服务提供者进行token校验

资源服务提供token合法性校验

  • 资源服务需要校验token的合法性,相对复杂
  • 受理了校验token合法性的业务,不能为其他应用提供服务,接口受制。

网管中心进行token校验

网管中心是掌管一切请求的入口,在这一层做token校验也是极为合理的。

  • 就如同需要校验请求是否登录一样,在网管中心校验token
  • 接口不受理校验token合法性的业务,接口可以作为其他服务提供者。
  • 实现相对复杂

授权服务进行token校验

授权服务提供token合法性校验,通过feign将请求再转发到资源服务

  • 把所有与授权相关的处理都统一在一个应用处理。
  • 授权服务的压力甚至比资源服务压力更大,因为所有请求全都要经过授权服务,所以授权服务也需要多台部署。
  • 接口不受理校验token合法性业务,接口可以作为其他服务提供者。

从架构上来说,更加推荐使用网管中心进行token校验,业务方接口方可复用。授权服务进行token检验亦有其优势,业务方接口亦可复用,但是服务压力大。

后记

OAuth2.0 目前已经被各大互联网公司所使用,足以证明它的优秀与不凡。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 1、什么是OAuth2.0协议? OAuth2.0(Open Authorization 2.0,开放授权)协议是...
    中峰阅读 6,361评论 5 6
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 什么是三方授权? 第三方授权就是,委托第三方来对既定的用户进行鉴定,鉴定成功之后,下发信任凭证,信任凭证和用户挂钩...
    一只小哈阅读 32,565评论 2 21
  • 过程都是一样的: 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并...
    米刀灵阅读 2,948评论 0 1
  • 原文地址:http://www.sanjinbest.com/blog/b6ec839d56c04ca387b95...
    木子小三金阅读 8,567评论 0 37