点融网开源权限控制框架Uniauth简介

Uniauth是一个基于CAS和Spring Security开源产品而开发的权限控制框架,它的目标是服务于点融网内部的各个子系统,只要集成了该框架,就能以很小的代价实现认证和授权功能。而权限设计是开发系统中不可规避的一个环节。

为什么要做权限控制框架

每个公司内部都有大量的子系统,点融网也不例外。

这些子系统有为普通员工服务的,比如各种OA系统;有为专门业务操作人员服务的,比如电销系统,库存管理系统;也有为技术人员服务的,比如我们常见的git,confluence,jira等系统。

点融网开源权限控制框架Uniauth简介_第1张图片

很多时候,这些子系统都有各自独立的账户体系和权限控制方式。当有新员工入职时,需要在各个必须的子系统中为其开设帐号并受以权限,浪费了大量的人力物力。而且,各个子系统权限控制的模式参差不齐,权限控制的数据模型也不尽相同,很多时候都留有安全隐患。比如,使用Fiddler等工具拦截http报文后,修改一些参数便可以访问到本不该被访问到的隐私内容。

另外,作为使用这些子系统的员工也比较痛苦,因为他们有可能在这些子系统中设置不同的密码,密码记忆混乱甚至忘记密码也常有发生。

如果这些子系统可以集成一套成熟的权限控制框架,使用唯一的一套账户体系,能够完成统一登录和登出(Single Sign On/Off),甚至各个子系统的管理员可以自行分配属于自己系统的组、角色、权限等信息,将会是一件大快人心的事!

综上所述,您可能认为我们确实应该设计一套权限控制的框架来帮我们来做这件事。在设计这套框架之前,让我们来看看需要做权限控制的一些常见业务场景。

需要权限控制的常见业务场景

页面级别(视图层)

控制菜单展现,按钮显示,数据渲染

方法访问控制

方法和类,事前和事后,aop切点语法支持

数据过滤

同样一份数据,对不同的人而言看到的数量或属性不同

请求的URL的拦截

只有属于某些/个角色的登录用户才可以访问某些模式的URL

某些方法调用需要将入参和当前用户相关联的情况

比如对于密码修改方法,登录人员的只能修改自己账户的密码

更细粒度的控制

使用ACL对域对象制定更细粒度的权限访问策略

其他更多种的访问控制策略的引入

比如基于IP(支持CIDR表示),基于访问时间段,remember me功能等等

可以看到,做到尽善尽美的权限控制十分不易,如果自己去实现所有这些业务场景的控制将花费大量力气,而且也不一定能做好。

那究竟该如何这个设计权限控制框架,或者说它的设计原则又是什么呢?

Uniauth的设计原则

我们认为,Uniauth的设计原则应该体现在以下方面:

兼容目前子系统的权限控制模型

子系统的权限模型可以适配转换进入新系统的权限模型。

具有广泛的第三方鉴权系统或协议的可接纳性

需要广泛的鉴权系统/协议的集成支持,包括但不限于:sso,oauth,ntlm/kerberos,openid,ldap/ms active directory,saml...因为说不清未来要集成什么东西。但有需要时可以通过配置,或以较小的代价接入。

具有良好扩展性

扩展性表现在认证和授权的各个阶段和环节,每个环节都有默认实现,但可以提供途径根据需要进行覆写和干预。这通常发生在子系统认为框架提供的某个环节不爽的场景,比如我觉得公共登录页很丑,我要定制自己的登录页。

方便的组,角色,人员权限分配和控制

当新加入业务操作人员时,管理人员只需要简短操作就可以方便的进行账户权限分配,控制,管理,权限开关设置等,

代码侵入性

权限控制对系统业务级代码无侵入性,或有较少侵入性。

使用成熟解决方案,不重复造轮子

使用业界久经考验的成熟开源方案,少些代码,遇到问题通过社区很快得到解决。

基于以上设计原则,我们选择了CAS + Spring Security的开源组合来作为我们Uniauth框架开发的基础。

Uniauth简介

Uniauth是一个基于CAS和Spring Security开源产品而开发的权限控制框架,其中统一认证功能由CAS提供,当访问多个集成了Uniauth框架的业务子系统时,只要用户登录一次,便可以访问所有其他业务子系统(SSO功能);授权功能由Spring Security提供,所有Spring Security的功能都可以使用。

同时我们对Spring Security的某些常用关键功能进行了封装和再增强,以便使您更专注于权限控制的业务实现(比如通过注解,表达式或JSP Security Tag)而无需了解Spring Security的底层配置和机制。所以,Uniauth框架基于Spring Security,而又不仅仅是一个Spring Security。

事实上,我们提供的是一个定制版的Spring Security框架,更好用,更强大,这主要体现在以下几点:

1、无缝集成CAS实现SSO

在集成Uniauth框架后,用户访问本业务系统,一旦检测到用户未登陆,会被自动引导到CAS统一登录页,完成登录后再跳转到业务系统,业务系统可以自动获取该用户的基本属性,如用户名,域信息,角色信息等(还可对属性进行扩展,见下文)。同时,无需再登陆即可访问其他集成的业务子系统。

2、对XML和数据库两方定义的URL拦截数据进行合并

在普通情况下使用Spring Security,对URL拦截的定义位于其配置文件的标签下的中,如:

如果进行定制的话,常规做法是实现FilterInvocationSecurityMetadataSource接口,用于加载预定义在数据库中的intercept-url定义,以便用于运行时权限判断。

但上面两种情况通常是互斥的,这是因为配置文件中的定义由Spring Security框架在启动时加载,并且注入到一个FilterSecurityInterceptor实例中;来自数据库定义的MetadataSource会被注入到另外的一个自定义FilterSecurityInterceptor实例中。在运行时权限判断中,两个FilterSecurityInterceptor无论谁先执行谁后执行,都会在基于Role的判断中影响或覆盖另外一个。

在Uniauth中会自动对这两边定义的intercept-url进行合并,并注入到同一个FilterSecurityInterceptor实例中,所有intercept-url定义都会起作用。

3、在URL定义中寻找最优匹配请求路径的条目

在权限控制的开始阶段,我们通常会做出粗糙控制,比如规定只有登陆用户才能访问本业务系统:

随着业务的不断精细化,我们可能会定义各种不同匹配模式(基于ant或正则),如只有ROLE_ADMIN角色才能访问/admin开始的url:

在普通的Spring Security使用中,这两个定义的先后顺序很重要,因为它会自上而下进行扫描,一旦发现有一条路径匹配就会选用。

这样就会产生问题,比如一个普通用户(ROLE_USER),访问了/admin/update/info这个url,因为第一条定义表明只要是认证过的用户就可以访问所有形式的url,这样就造成了普通用户也可以访问管理员才可以访问的页面。

在Uniauth中不会产生这个问题,Uniauth会把来自配置文件或数据库中的所有匹配请求路径的url定义聚集起来,然后从中选择一个最匹配(最长匹配)的进行选用,无论url定义的顺序是什么。

对应于上面的例子,/admin/update/info这个请求路径对于两个url定义都匹配,但是显然更匹配第二个,所以会选用第二个定义。但第二个要求是ROLE_ADMIN角色才能访问,用户本身是ROLE_USER,所以会拒绝他的请求。

我们建议对所有需要保护的资源url进行定义切分,因为不被保护的url资源属于公用资源,谁都可以访问。

4、添加hasPermission表达式支持

我们在Uniauth中添加了对hasPermission表达式的支持,hasPermission表达式是最精细的,最彻底的权限控制方式,用于判断当前登陆用户对于某个业务对象是否有某种访问权限。

在Uniauth中,您只要实现UniauthPermissionEvaluator接口,或者从UniauthPermissionEvaluatorImpl扩展,根据自己的需要覆写两个hasPermission方法或其中一个即可,hasPermission表达式可以内嵌在@PreAuthorize注解中,或者也可以使用在JSP安全标签中。

5、对用户登录session进行并发控制

为了安全考虑,我们配置了并发Session访问控制,即Concurrent Session Control。当同一个账户在不同的Session会话中访问同一个业务系统时,第二个Session会将第一个Session会话踢出,如果用户在第一个Session会话中继续活动,会被提示“对不起,您的会话超时,或者您的账号在另外一个窗口中已登录,导致本次会话结束,如有需要,请重新登录!”,引导用户登陆或彻底退出登录系统。

6、对UserDetails对象随需进行扩展

UserDetails在Spring Security中是一个很重要的对象,它代表了通过认证后的用户实体,即principal对象。

我们通常使用principal对象在@PreAuthorize中结合SpEL(Spring Expression Language)进行基本的权限控制,看看当前用户实体是否有权限进行某个方法的调用,这是权限控制中很重要的一个环节。比如对如下方法控制:

@PreAuthorize("hasRole('ROLE_SUPER_ADMIN') and principal.permMap['DOMAIN'] != null and principal.permMap['DOMAIN'].contains('techops')")

public Response resetPassword(@RequestBody UserParam userParam) { ... }

从UserDetails中可以获取到跟当前登录用户相关联的属性,比如用户名,密码,账户是否被锁定,密码是否过期,用户被授予的角色列表等基本信息。

在Uniauth框架中对UserDetails进行了基本扩充,除包含上述用户信息外,我们还添加了当前用户登录的域信息,用户在该域上的权限(privilege)信息等。

另外,每个子系统可能有不同业务需要,我们添加了UserInfoCallBack回调接口,只要业务系统实现了它,就能在UserDetails中添加自己需要的扩展用户属性,比如该用户所有的组列表信息,或者跟自己业务系统相关的其他用户属性信息等。

在大部分情况下,添加扩展属性的目的是用于上面介绍的@PreAuthorize表达式判断,或者用于自身业务操作的其他目的。

Uniauth框架和子系统的集成框架图

点融网开源权限控制框架Uniauth简介_第2张图片

Uniauth系统已经上线,欢迎有集成需求的系统跟我们联系,我们将竭诚提供集成支撑服务。

本文作者:许增伟(David Xu),现任点融网成都团队架构组开发工程师,主要开发与业务无关的横切系统和组件,如权限控制系统,消息组件,日志组件和一些公共管理工具等等。

你可能感兴趣的:(点融网开源权限控制框架Uniauth简介)