在之前的两篇文章《Java权限框架1——业务痛点与技术调研》,《Java权限框架2——sureness框架介绍》,重点介绍了在做Java web系统时的权限设计方案,但是感觉这个系列不是很通用,很难继续往下深入。
因此我新开设了专栏《web开发》,将分享一些我在做web开发时的心得总结,包括权限系统设计、Django实战、常用web技术、前端知识等等。今天先继续探讨权限设计,讲明白经常用到的各个技术知识点。
认证 (Authentication) 和授权 (Authorization)
关于认证和授权,是做权限设计的两块基石,任何权限框架,都逃不过这两点。
比如shiro框架:
shiro对着两者的解释是:
Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.
Authorization: The process of access control, i.e. determining ‘who’ has access to ‘what’.
简单来讲,它们两者的区别是:
认证 (Authentication): 你是谁。系统要做身份/用户验证。
授权 (Authorization): 你有权限干什么。系统要已经知道了你是谁,但还要看你是否有特定权限访问特定资源。
RBAC模型
系统权限控制最常采用的访问控制模型就是 RBAC 模型 。
RBAC 即基于角色的权限访问控制(Role-Based Access Control)。这是一种通过角色关联权限,角色同时又关联用户的授权的方式。
在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。
最经典的RBAC的数据库实现就是五表设计,即三张实体表(用户,角色,权限)加两张关系表(用户-角色,角色-权限,两者都是多对多关系)。
另外谈下我对权限表的理解。其实一个web系统的权限控制可以分为多种维度,如果是前后端分离的项目,需要控制后端api的粒度,也需要控制前端所能展示的菜单或按钮的粒度。
对于API,菜单,按钮都可以理解为资源,他们可以共同放置在权限表里用字段区分,或者分开放置,但不变的是通过角色去跟权限做多对多的关联。
我在这里推荐一个项目,github.com/tomsun28/bo…
这里面提供了一个演示环境:http://47.110.55.246/ , 算我目前见过的最符合权限设计模型的前端页面设计了。
cookie和session
Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
Cookie 存放在客户端,一般用来保存用户信息,典型场景举例:
- 我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。
- 使用 Cookie 保存 Session 或者 Token ,向后端发送请求的时候带上 Cookie,这样后端就能取到 Session 或者 Token 了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。
- Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为 HTTP 协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在 Cookie
Session 的主要作用就是通过服务端记录用户的状态,服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
另外要特别注意的是,Session不能脱离Cookie而存在,一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID 的方案的话, 如果客户端禁用了 Cookie,那么 Session 就无法正常工作。但是,并不是没有 Cookie 之后就不能用 Session 了,比如你可以将 SessionID 放在请求的 url 里面baidu.com/?Session_id… 。这种方案的话可行,但是安全性和用户体验感降低。
在生产实践中,我们基本都是对服务器水平扩展为多个节点进行部署,我们通常会使用一个所有服务器都能访问到的数据节点(比如Redis缓存,关于分布式缓存的内容,我后续将在《分布式》专栏里继续讨论)来存放 Cookie或Session 信息。为了保证高可用,数据节点尽量要避免是单点。
另外再分享一个开发心得,我曾经负责过一个项目,它是使用session保存的用户信息,我需要去用代码来访问这个系统的数据,于是首先采用了模拟登录的方式,需要去拿网页上的sessionId的值,这个要去修改tomcat的session-config为httponly为false,这就违反了安全性问题,具体可以看我这一篇文章《未知系统的开发挑战》
常见登录方案
在这一节,我将介绍一些常用的登录方案。
JWT
在前面的讨论中,我们需要把Cookie或Session的登录信息保存在服务器端,这里介绍一种利用客户端保存登录信息的方式,使用 Token 即可。JWT (JSON Web Token) 就是这种方式的实现,通过这种方式服务器端就不需要保存 Session 数据了,只用在客户端保存服务端返回给客户的 Token 就可以了,扩展性得到提升。
JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。
JWT 由 3 部分构成:
- Header : 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
- Payload : 用来存放实际需要传递的数据
- Signature(签名) :服务器通过Payload、Header和一个密钥(secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
在基于 Token 进行身份验证的的应用程序中,服务器通过Payload、Header和一个密钥(secret)创建令牌(Token)并将 Token 发送给客户端,客户端将 Token 保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization 字段中:Authorization: Bearer Token。
详细流程如下:
- 用户向服务器发送用户名和密码用于登陆系统。
- 身份验证服务响应并返回了签名的 JWT,上面包含了用户是谁的内容。
- 用户以后每次向后端发请求都在 Header 中带上 JWT。
- 服务端检查 JWT 并从中获取用户相关信息。
参考:jwt.io/
SSO
SSO(Single Sign On)即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了百度后,也能登录百度网盘,百度贴吧等等。
单点登录非常常用,我做过很多公司内部系统的开发,都会涉及到单点登录。
单点登录有以下好处:
- 用户角度:用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
- 系统管理员角度:管理员只需维护好一个统一的账号中心就可以了,方便。
- 新系统开发角度:新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
OAuth 2.0
OAuth 是一个行业的标准授权协议,在于允许用户在不告知第三方自己的帐号密码情况下,通过授权方式,让第三方服务可以获取自己的资源信息。
实际上它就是一种授权机制,它的最终目的是为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够通过该令牌获取相关的资源。
OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录的时候一般就是使用的 OAuth 2.0 协议。
我曾经在项目里用过企业微信登录,这也是一种OAuth2.0的登录方式,可以参考微信的官方文档:
下面简单说明OAuth2中最经典的Authorization Code模式,流程如下:
流程图中,包含四个角色。
- ResourceOwner为资源所有者,即为用户
- User-Agent为浏览器
- AuthorizationServer为认证服务器,可以理解为用户资源托管方,比如企业微信服务端
- Client为第三方服务
调用流程为:
- 用户访问第三方服务,第三方服务通过构造OAuth2链接(参数包括当前第三方服务的身份ID,以及重定向URI),将用户引导到认证服务器的授权页
- 用户选择是否同意授权
- 若用户同意授权,则认证服务器将用户重定向到第一步指定的重定向URI,同时附上一个授权码。
- 第三方服务收到授权码,带上授权码来源的重定向URI,向认证服务器申请凭证。
- 认证服务器检查授权码和重定向URI的有效性,通过后颁发AccessToken(调用凭证)
(3和4的调用为后台调用,不通过浏览器进行)
在实际开发时,可参考微信官方提供的缓存建议,避免频繁请求微信服务器。
缓存方案建议:通过OAuth2.0验证接口获取成员身份会有一定的时间开销。对于频繁获取成员身份的场景,建议采用如下方案:
- 企业应用中的URL链接直接填写企业自己的页面地址
- 成员操作跳转到企业页面时,企业后台校验是否有标识成员身份的cookie信息,此cookie由企业生成
- 如果没有匹配的cookie,则重定向到OAuth验证链接,获取成员的身份信息后,由企业后台植入标识成员身份的cookie信息
- 根据cookie获取成员身份后,再进入相应的页面