背景:
门户网站分为个人用户,个人开发者,机构用户,页面不同的操作需要特定的用户才能操作。如api申请,密钥下载只有机构用户才可操作,重置密码需要登录后才可操作。
管理端分为系统管理员,开放平台管理员,银行管理员,不同的角色具有不同的权限,
权限按资源划分
服务间调用也需要进行保护,如调用发送短信接口,调用发送站内信的接口,不能不加限制地对外暴露。
因此需要以接口为粒度进行统一鉴权,在接口处注明需要的权限,需注明请求来源:门户,管理端,服务间调用。如果是门户或管理端,还可标注需要的角色或资源。可校验多个来源。
技术设计:
对于门户网站和管理端,用户登录成功后生成包含用户信息(userId)的JWT token,前端保存在vuex中。每次操作请求将token放入front- Authorization和manage- Authorization以供服务端校验。其中front和manage代表源自门户和管理端。
服务间调用发送http请求时,在header处添加service- Authorization,值为有效期为30s的JWT token,service代表源自服务间调用。
通过注解标记需要校验的接口,并添加所需要的请求来源和资源名称,如果匹配则放行,否则报无权限请求。
利用拦截器对请求进行切面操作,如果该方法加@AuthRequest注解则寻找header,若包含front- Authorization,manage- Authorization或service- Authorization,则对该token进行JWT校验。其中front和service校验较为简单,直接校验即刻,manage校验token成功后还需从token中获取userId,通过服务间调用判断该用户是否拥有该资源。如果门户和管理端校验成功,从token中取出userId,role放入attribute,供接口使用。
引入该包还为feign调用添加service- Authorization的header
流程图:
说明:
门户网站,管理端和服务间调用通常通过gateway,但是统一校验不在gateway,而在各自服务,原因如下:
- 更安全,有的请求可能直接通过服务调用,而非gateway,因此需要在服务的接口层面做校验
- 配置更方便
权限是一种安全策略,用户只可访问被授权的资源。因此需要采取策略控制用户的行为。
涉及到以下几个方面:
- 用户认证:如何校验用户的合法性并保存用户的登录状态
- 权限校验:如何保护服务端的接口,用户是否有资格调用接口,包括:从web端发送来的请求,服务间的调用
- 权限管理:如何给用户分配资源,RBAC
权限功能在服务从单体转向微服务架构面临如下挑战:
- 如何保证用户的登录状态。单体中可将登录状态保存至session中,但微服务中应用被分为多个服务,服务应该是无状态的,不能保存用户的登录状态;
- 微服务拆分后,调用来源不仅仅是客户端,还有来自服务间的调用,因此需要全面考虑多个来源的调用
- 权限校验功能不能散落在各个服务中,应统一处理
- 前后端分离,只做数据交互,前端路由/按钮资源化,如何管理资源
微服务应用的权限功能应具有特点:
- 与业务系统解耦,功能独立,职责单一
- 保证可用性,当权限服务不可用时,不能改变业务系统的行为
- 保证性能,避免因鉴权导致响应时间增加
- 对已有代码侵入性小,改造方便
现有的解决方案:
- Oauth2:授权框架
- JWT token:认证协议
- Security:有些重
- Shiro:可做权限认证,密码加密等,但不太适用于微服务环境,SecurityManager
- 分布式session:将username,session id作为key存储至分布式存储中,用户访问微服务时,可以从分布式存储中判定。保证了高可用和可扩展。缺点是存在安全隐患。
经过比较,决定自行研发一套权限校验框架,包括:
- JWT Token进行用户认证及鉴权
- RBAC权限分配体系
架构图:
Token设计
一段字符串,用户登陆成功后服务端生成返回至客户端保存,作为后续已登陆状态的凭证。
优点:
- 每次请求将token放入header中,可避免CSRF
- 自身无状态,可在服务间共享,服务端不保存token仍可校验其合法性。(不用在服务端保存k-v)
应具有以下特点:
- 一个Token就是一些信息的集合,包含如username,phone-number,资源等信息;
- 可作为用户认证的凭据。因为token是被签名的,服务端可对cookie和HTTP Authrorization Header进行Token信息的检查,如果校验合法,可认为该请求是安全的
- Token有一定的有效期,保证安全性
- Token在有效期内有效,无法通过自身失效,除非通过放入redis辅助校验
过期策略:
Token过期后应进行续签,而不是跳转到登陆画面,造成糟糕的用户体验。Token失效时间过长会存在安全性隐患,过短会造成频繁的refresh token。好的体验应该是在用户无感知的情况下进行token刷新。
- 服务端保存token状态,用户每次操作均刷新token有效期,可做到一段时间没有操作即登出,但这种方案对内存压力较大,每次操作都要访问,失去了token可被验证的便捷性
- refresh token:用户登陆成功后返回token及refresh token,token过期后通过refresh token续签token,token有效期短而refresh token有效期长,refresh token失效后认为登出。可实现每次登陆保存登录状态一定时间。可以避免对内存的频繁读写
何时refresh token:
- 前端建立定时任务每隔一定时间通过refresh token获取新token
- 收到响应后,如果响应为token过期,通过refresh token获取新token,再次发送请求,如果refresh token过期,说明需重新登录
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。