欢迎大家关注我的微信公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。

一、背景

随着业务的发展,越来越多的系统需要数据往来。那对外提供的接口也越来越多,而且各个接口散落在不同的项目中被调用,多了的话排查问题困难且混乱。基于这个痛点,我们有必要打造一套开放平台来管理各个 api 的调用情况。

二、开放平台设计

我们先从整体的功能需求来分析,主要有以下几点:

  • 开发者身份注册与数据权限范围授权
  • 开发者获取相关资料(接口文档、使用说明、对接人联系方式等)
  • 平台方接入管理,申请审核流程、服务配置、服务管理、参数配置等
  • 平台方运营管理,业务交易管理及统计报表分析
  • 安全层面需求,加密、应用秘钥、应用接口权限控制、访问黑白名单、字段脱敏还原等
  • 性能方面要求,客户端缓存、服务端缓存、缓存等


这里老周给出自己的一个架构,大家可以参考下:

上面的设计方案更多的是针对比较大型的公司,想要把整个开平的能力建设完善。但市场上更多的是中小型公司,它们没有太多的人力去开发与建设这么全面的开放平台。

那如果是中小型公司,那它们的开放平台如何不费很大精力去实现呢?不管中小型还是大型公司的开放平台,上面说的那个图中其它部分可以省略,但安全机制是必需的,也就是架构图中的统一鉴权。试想一下,作为提供给第三方调用接口的开放平台,如果安全机制不能保障,那外部谁都可以来调用你们公司的内部资源,危害可想而知。

老周下面就来针对不同的业务场景来给出相应的开放平台安全机制的保障,也就是根据不同类型的网站给出相对应的开放平台设计方案。

三、小型网站

3.1 基于 session 的登录认证

在传统的用户登录认证中,因为 http 是无状态的,所以都是采用 session 方式。用户登录成功,服务端会保存一个 session,当然会给客户端一个 sessionId,客户端会把 sessionId 保存在 cookie 中,每次请求都会携带这个 sessionId。服务器收到 sessionId,找到前期保存的数据,由此得知用户的身份。


对于小型网站,特别是单机系统,基于 session 的登录认证方案已经够用了,而且简单高效。

四、中型网站

随着用户量的增多,上面基于 cookie + session 的这种模式缺点就显现出来了,这种模式通常是保存在内存中,而且服务从单服务到多服务会面临 session 共享问题,开销也随即越来越大。

那中型网站的安全认证机制是啥呢?接下来 JWT(JSON Web Token) 即将登场,关于 JWT 的概念与原理,老周这里觉得还是有必要说一下。

4.1 JWT 的概念

4.1.1 什么是 JWT?

JWT 是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递 json 对象,传递的信息经过数字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA的公钥/私钥对来签名,防止被篡改。

说白了 JWT 就是一套基于 token 的身份认证的方案,可以保证安全传输的前提下传送一些基本的信息,以减轻对外部存储的依赖,减少了分布式组件的依赖,减少了硬件的资源。

可实现无状态、分布式的 Web 应用授权,JWT 的安全特性保证了 token 的不可伪造和不可篡改。

本质上是一个独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以及您可以存储任何其他信息(自包含)。任何人都可以轻松读取和解析,并使用密钥来验证真实性。

4.1.2 JWT 令牌结构

JWT 令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

  • Header
    头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA),例如:

    {"alg": "HS256", "typ": "JWT"
    }
    

    将上边的内容使用 Base64Url 编码,得到一个字符串就是 JWT 令牌的第一部分。

  • Payload
    第二部分是负载,内容也是一个 json 对象,它是存放有效信息的地方,它可以存放 jwt 提供的现成字段,比如:iss(签发者),exp(过期时间戳),sub(面向的用户)等,也可自定义字段。 此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第二部分负载使用 Base64Url 编码,得到一个字符串就是 JWT 令牌的第二部分。 一个例子:

    {"sub": "1234567890", "name": "微信公众号【老周料架构】", "iat": 1516239022
    }
    
  • Signature
    第三部分是签名,此部分用于防止 jwt 内容被篡改。 这个部分使用 base64url 将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用 header 中声明签名算法进行签名。
    secret:签名所使用的密钥。

    HMACSHA256 ( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret
    )
    

    验签过程描述:获取 token 值,读取 Header 部分并 Base64 解码,得到签名算法。根据以上方法算出签名,如果签名信息不一致,说明是非法的。

4.2 JWT 的流程


4.3 JWT 代码案例

如果你们公司有第三方应用接入的开放平台,那可以在里面走相应的接入流程得到 appId 和 appSecret。如果没有的话,那可以简单点与第三方约定相应的 appId 和 appSecret。老周这里假设你们已经约定好了,我这里直接放在请求头里来获取 token,还有其它的方式,比如放在请求参数或者 cookie 里。

4.3.1 maven 依赖

<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.1</version>
</dependency>

4.3.2 JWTUtil 工具类

public class JWTUtil {private static String SECRETE = "default_secrete";private static String APP_ID = "zhifubao";private static String APP_SECRETE = "123abc";/*** 传入 appId、appSecret 进行验证* @param appId 应用id* @param appSecret 应用密钥* @return 返回一个加密 JWT token*/public static String getToken(String appId, String appSecret) {String token = JWT.create()// 存放 payload 数据.withClaim("appId", appId).withClaim("appSecret", appSecret)// 使用 SECRETE 对称加密生成 signature.sign(Algorithm.HMAC256(SECRETE));return token;}/*** 验证 token* @param token* @return*/public static boolean verifyToken(String token) {HashMap<String, String> map = new HashMap<>();// 通过 SECRETE 和相同的对称加密算法反加密DecodedJWT jwt = JWT.require(Algorithm.HMAC256(SECRETE)).build().verify(token);// 获得你储存的 payload 信息String appId = jwt.getClaim("appId").asString();String appSecret = jwt.getClaim("appSecret").asString();if (APP_ID.equals(appId) && APP_SECRETE.equals(appSecret)) {return true;}return false;}
}

4.3.3 JWTController 类

@RestController
public class JWTController {@RequestMapping("/getToken")public String getToken(@RequestHeader("appId") String appId, @RequestHeader("appSecret") String appSecret) {return JWTUtil.getToken(appId, appSecret);}
}

4.3.4 测试

拓展:这个私钥 secrete 是固定的,为了加强安全,你甚至可以使用动态的 secrete 私钥,
例如:动态私钥 = 静态私钥 + 用户的 ip,这样即使别人得到了用户的 token,也会因为 ip 不一致而访问失败。

拿到了应用资源服务器的 token 令牌了,那我们拿这个令牌去访问相应的资源看看。

@RequestMapping("/getResource")
public String getResource(String resourceId) {return resourceId + " 资源获取成功";
}

简单模拟一个请求,直接返回该资源获取成功。我们接下来就用 postman 工具来模拟一下这个资源服务器的这个接口请求。


认证失败了,这是因为我们没有在请求头里填刚刚获取的 token。我们把通过调用 getToken 接口获取的 token 值放在在请求头,然后认证通过,获取到了资源服务器的资源。


4.3.5 继续追问

这里你有可能问了,老周,这里咋就带上 token 在请求头就可以获取到了资源服务器的资源啊。

我把代码贴出来,你一看就知道了。

这里写了一个 token 的拦截器,对请求头的 token 进行验签,通过才放行。

@Component
public class TokenInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token");if (token != null) {boolean result = JWTUtil.verifyToken(token);if (result) {System.out.println("通过拦截器");return true;}}response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");try{response.getWriter().append("认证失败,无效的token令牌!");System.out.println("认证失败,无效的token令牌!");} catch (Exception e) {e.printStackTrace();response.sendError(500);return false;}return false;}
}

这里有个拦截器配置类,把需要拦截的 api 路径放进来,然后会对某个 api 进行细粒度的管控。

@Configuration
public class IntercepterConfig implements WebMvcConfigurer {private TokenInterceptor tokenInterceptor;public IntercepterConfig(TokenInterceptor tokenInterceptor){this.tokenInterceptor = tokenInterceptor;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {List<String> excludePath = new ArrayList<>();excludePath.add("/getResource/");excludePath.add("/static/**");  //静态资源registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns(excludePath);WebMvcConfigurer.super.addInterceptors(registry);}
}

这就实现中型网站安全认证机制了,细心的读者可能会发现,这个 token 是固定的,会存在一些不安全。是的,我上面也说了,可以用动态的 secrete 私钥或者 token 过期机制来继续保证更高的安全性。

五、大型网站

大型网站的话,针对中型网站的方案就不太可行了,为什么呢?由于大型网站的请求流量很大,而 token 由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token 的签名验签操作也会给 cpu 带来额外的处理负担。可以采用微服务统一认证方案 Spring Cloud OAuth2,那什么情况下需要使用 OAuth2?

  • 第三方授权登录的场景:比如,我们经常登录一些网站或者应用的时候,可以选择使用第三方授权登录的方式,比如:微信授权登录、QQ授权登录、微博授权登录等,这是典型的 OAuth2 使用场景。
  • 单点登录的场景:如果项目中有很多微服务或者公司内部有很多服务,可以专⻔做一个认证中心(充当 认证平台⻆色),所有的服务都要到这个认证中心做认证,只做一次登录,就可以在多个授权范围内的服务中自由串行。

5.1 OAuth2 构建微服务统一认证服务思路


注意:在我们统一认证的场景中,Resource Server 其实就是我们的各种受保护的微服务,微服务中的 各种 API 访问接口就是资源,发起 http 请求的浏览器就是 Client 客户端(对应为第三方应用)。

5.1.1 搭建认证服务器(Authorization Server)

5.1.1.1 maven 依赖文件


5.1.1.2 application.yml 文件


5.1.1.3 OauthServerApplication9999 启动类

5.1.1.4 认证服务器配置类


5.1.1.5 认证服务器安全配置类


5.1.1.6 测试

5.1.1.6.1 获取 token

http://localhost:9999/oauth/token?client_secret=abcxyz&grant_type=password&username=admin&password=123456&client_id=client_riemann

endpoint:/oauth/token

获取token携带的参数
client_id:客户端id
client_secret:客户单密码
grant_type:指定使用哪种颁发类型,password
username:用户名
password:密码


5.1.1.6.2 校验 token

http://localhost:9999/oauth/check_token?token=28317df7-4036-4bbb-8bb3-12f71fa07802


如果出现以上页面,表明 token 过期了,设置的是 20s。所以要在 20s 以内校验才会生效。

下面才是 token 校验成功的效果:


5.1.1.6.3 刷新 token

http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client_riemann&client_secret=abcxyz&refresh_token=68582d02-3a1d-4c31-ae22-ac7e84824d0d


5.1.2 搭建资源服务器(希望访问被认证的微服务)

5.1.2.1 资源服务 Resource Server 配置类


5.1.2.2 测试



此测试结果也印证了代码的效果


我们加上带上token测下看看:


5.2 OAuth2 统一认证服务思考

  • 当我们第一次登陆之后,认证服务器颁发 token 并将其存储在认证服务器中,后期我们 访问资源服务器时会携带 token,资源服务器会请求认证服务器验证 token 有效性,如果资源服务器有很多,那么认证服务器压力会很大。
  • 另外,资源服务器向认证服务器 check_token,获取的也是用户信息 UserInfo,能否把用户信息存储到令牌中,让客户端一直持有这个令牌,令牌的验证也在资源服务器进行,这样避免和认证服务器频繁的交互。
  • 我们可以考虑使用 JWT 进行改造,使用 JWT 机制之后资源服务器不需要访问认证服务器。

5.3 JWT 改造统一认证授权中心的令牌存储机制

JWT 在上面中型网站那一节说过了,这里就不重复说了,老周直接上代码了。

5.3.1 认证服务器端 JWT 改造(改造主配置类)

5.3.2 修改 JWT 令牌服务方法


5.3.3 认证服务器端测试


可以看出,使用 jwt 令牌生成的 access_token 和上一篇的不一样。

我们用这个网站:https://jwt.io/#encoded-jwt 把该 access_token 进行解码,解码如下:


其他两个验证 token、刷新 token 跟上一篇类似。

5.3.4 资源服务器校验 JWT 令牌

不需要和远程认证服务器交互,添加本地 tokenStore。


5.3.5 源服务器端测试



这样就完成了资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。

六、总结

老周首先从开放平台的整体功能设计来分析了有如下几个要点:开发者认证、开放平台内部管理系统、安全机制以及性能。

但考虑很多公司它们没有太多的人力去开发与建设这么全面的开放平台,故抓住其中的最核心的一点,那就是安全机制。

针对于安全机制来说,不同类型的网站有不同的安全机制保障。

  • 小型网站:基于 session 的登录认证,在小型网站特别是单机系统,这种方案够用了,而且简单高效;
  • 中型网站:到了中型网站,服务肯定是分布式部署的,这个时候小型网站中基于 session 的登录认证方案的缺点就暴露出来了。每个应用服务都需要在 session 中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将 session 信息带过去,否则会重新认证。我们还要通过 session 共享、session 黏贴等方案来解决。从而引入了第三方分布式组件,比如 redis,增加了系统的复杂性。并且 session 方案还有另一个缺点,比如基于 cookie,移动端不能有效使用等。所以中型网站的话基于 JWT 的 token 认证机制,服务端不用存储认证数据,易维护扩展性强,客户端可以把 token 存在任意地方,并且可以实现 web 和 app 统一认证机制。
  • 大型网站:到了大型网站,请求量也随之暴涨,中型网站的 token 认证机制的缺点也逐步暴露出来了,token 由于自包含信息,因此 一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token 的签名验签操作也会给 cpu 带来额外的处理负担。这个时候得采用微服务统一认证方案 Spring Cloud OAuth2,后面我们又对 OAuth2 进行了一些优化,因为大型网站的开平请求流量会很大,资源服务器会请求认证服务器验证 token 有效性,那么认证服务器压力会很大。另外,资源服务器向认证服务器 check_token,获取的也是用户信息 UserInfo,能否把用户信息存储到令牌中,让客户端一直持有这个令牌,令牌的验证也在资源服务器进行,这样避免和认证服务器频繁的交互。所以我们后续使用 JWT 进行改造,使用 JWT 机制之后资源服务器不需要访问认证服务器。性能以及安全机制都得到了有力保障。

看完希望对你有所帮助,有帮助的话,请不要吝啬你的点赞、评论、收藏,毕竟原创不易。 好了,我们下期再见。

开放平台设计方案与实践相关推荐

  1. 京东到家开放平台网关系统设计实践

    目录 一.平台介绍 二.服务设计 三.服务发布 四.服务授权 五.状态管理 六.流量管控 6.1 多维度流控 6.2 流控算法 七.总结 文|王多友 编辑|刘慧卿 一.平台介绍 京东到家开放平台,是一 ...

  2. 博时基金基于 RocketMQ 的互联网开放平台 Matrix 架构实践

    简介: Matrix 经过一年多的建设,目前已具备多渠道统一接入.第三方生态互联互通.基金特色交易场景化封装等功能特性.Matrix 通过建设有品质.有温度的陪伴,从技术上和体验上,让用户理解风险,理 ...

  3. 新浪微博开放平台开发-android客户端(2)

    引言 最近不是太忙,花了一些时间学习android的应用开发.经过两个星期的学习,学习的同时还写了很多的demo示例,可以从基本控件及基本动画效果等示例demo集合大放送,持续更新下载. 从这个星期开 ...

  4. 新浪微博开放平台开发-android客户端(3)

    引言 最近不是太忙,花了一些时间学习android的应用开发.经过两个星期的学习,学习的同时还写了很多的demo示例,可以从基本控件及基本动画效果等示例demo集合大放送,持续更新下载. 从这个星期开 ...

  5. 数据自治开放应用平台设计与实践

    数据自治开放应用平台设计与实践 陈德华, 潘乔, 王梅, 乐嘉锦 东华大学计算机科学与技术学院,上海 201620 摘要:围绕数据自治开放的数据管理新模式,提出了一套面向数据自治开放应用的整体解决方案 ...

  6. 蚂蚁金服高级技术专家李福喜 :蚂蚁开放平台技术路线及行业实践

    8月30-31日20:00-21:30,一场别开生面的技术大会-- "蚂蚁金服&阿里云在线金融技术峰会"将在线举办.本次将聚焦数据库.应用架构.移动开发.机器学习等热门领域 ...

  7. 物联网开放平台:三种实践模式

    Facebook的一炮而红,让不少人看到了开放平台的强大吸引力.谷歌.微软等互联网巨头,开始竞相推出自己的开放平台战略,以求追赶互联网发展的新潮流.然而,庖丁技术认为,物联网设备对开放平台的需求远大与 ...

  8. java接口开放平台设计,OpenApi开放平台架构实践

    WebAPI 开放平台架构实践 导读 背景 需求 场景 架构设计 总结 背景 随着业务的发展,越来越多不同系统之间需要数据往来,我们和外部系统之间产生了数据接口的对接.当然,有我们提供给外部系统(工具 ...

  9. 美团开放平台SDK自动生成技术与实践

    美团开放平台为整个美团提供了20+业务场景的开放API,为了使开发者能够快速且安全的接入美团开放平台,美团开放平台提供了多种语言的SDK来提高开发者的接入效率.本文介绍了美团开放平台如何自动生成SDK ...

最新文章

  1. 用PaddlePaddle打比赛!
  2. mysql主键自增长空缺_Mysql 主键自增长auto_increment问题分析
  3. CMake 中的 PUBLIC,PRIVATE,INTERFACE
  4. 20级四班班级管理规章制度
  5. 硬盘序列号查询软件_【西数硬盘购买指南】干货——西数移动硬从购买到验证体会心得...
  6. 阿里云php-7.2.12 安装
  7. 每天Leetcode 刷题 初级算法篇-有效的括号
  8. 谁说程序员找不到女朋友,你们是不知道当程序员撩妹,一撩一个准
  9. 部门开始做技术talk
  10. WPS永久关闭热点、云服务、初始登陆界面
  11. 在计算机中正确的邮件写信格式,电子邮件格式怎么写
  12. 【看表情包学Linux】进程阻塞 | 轮询检测 | 基于非阻塞等待的轮询方案 | 进程程序替换 | exec 函数簇
  13. 初学者对PHP的总结,PHP_献给php初学者(入门学习经验谈),1.概要:学习任何语言都需要 - phpStudy...
  14. 7-10 计算工资 (15分)
  15. python切面异常处理_node端统一错误处理
  16. Source Insight 4.0打开一会就闪退 解决方法
  17. 安装Anaconda3-5.1.0-Linux-x86_64.sh报错
  18. 手动启动fabric例子marbles02
  19. verilog设计加法器
  20. (翻譯) 註解程式碼的13個建議 (C/C++)

热门文章

  1. java如何在窗口上显示数据_如何把Java程序窗口在屏幕中间显示
  2. python判断素数的方法简书_深入浅出RSA在CTF中的攻击套路
  3. cocos2dx fnt字体、自定义字体制作
  4. ASP一叶知秋 SaaS将会成为08重点
  5. 《历》·陕西帝王陵寝一览
  6. AdmExpress 国际仓储转运系统/海淘转运系统 1.0 正式版发布
  7. Bzoj2300 / 洛谷P2521 [HAOI2011]防线修建
  8. 海量数据处理算法—Bloom Filter
  9. java 知网 语义 相似度,基于知网语义相似度的中文文本分类研究 论文笔记
  10. 天嵌E9卡片i.mx6q-Linux12.04搭建nfs环境以及从nfs启动开发板