基于Spring Security 的JSaaS应用的权限管理
1. 概述
权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见。本文介绍的则是基于Saas系统架构的处理模型,SaaS应用的数据安全是目前大型企业比较担心的问题,因此,JSaaS的安全应用就显得非常重要。JSaaS平台不单是一款私有云的应用管理平台,更是一款可扩展开发的,适合于二次开发的租用的应用开发平台,如适合集团下有多个子公司多个子应用的开发。同时用于一个单位上使用,相当于只有一个租户的SaaS应用。本文从应用使用场景进行分析设计,并且基于Spring Security的开源安全框架上进行设计,以保证满足未来SaaS应用的数据安全要求。
2. Spring Security权限管理的原理
Spring Security 是一成熟的安全管理框架,大量应用于不同的系统中,其权限管理原理很简单,就是通过一组filter进行访问地址的拦截,通过判断用户的身份及其允许访问的权限,然后授权是否允许访问其下的资源。资源包括页面、逻辑代码中方法等。借用网上一图说明:
这些不同的Filter作用,请参考以下访问地址:
http://blog.163.com/yf_198407/blog/static/5138541120114272476265/
任何一个平台的数据访问都是需要授权的,授权只需要管理好两点,一是登录,另一个是授权访问需要的内容。Spring Security实现这两点非常容易,它已经提供了对应的接口及拦截点。
目前市面上大部分的平台都是基于角色控制访问的,因此,我们JSaas平台也是采用该办法,通过对角色或用户组进行授权,然后再把角色或用户组授权给用户即可,其原理图如下所示:
【说明】用户组包括的概念可以很广,如角色、部门、岗位、临时用户组等。我们在系统中只需要授权给用户组可访问哪一些资源,然后再把对应的用户组授权给对应的用户即可。为了后续以后平台对接其他用户的组织架构管理,我们对用户再进行一层隔离,即通过登录账号来实现登录的身份认证,而登录账号只需要通过关联用户即可。
【用户、用户组、资源之间的关系】
3.JSaaS平台的权限设计要点
3.1. 登录–身份认证
平台上有登录权限的实体我们称之为用户账号,也称之为身份认证,Security只需要实现UserDetails接口即可,登录的时候调用一下Security的安全认证接口即可。如下所示:
<!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userDetailsProvider" />
</security:authentication-manager>
<bean id="userDetailsProvider" class="com.redxun.saweb.security.provider.UserDetailsProvider" />
UsesrDetailsProvider只需要实现UserDetailsService的loadByUsername(String username)方法即可,登录的实体实现UserDetails的方法即可。用户组实现GrantedAuthority接口即可。
虽然Spring Security提供了登录的实现Filter,但我们可以用它默认的实现,但为了我们平台的后续的更多扩展及灵活性,我们决定提供自定义的登录方式,但需要在登录后,通知Spring Security框架,即设置该框架需要的一些参数数据,以使得其后续可以通过对应的Filter访问到需要的资源。以下为我们的LoginController的实现方式:
@Controller
@RequestMapping("/")
public class LoginController extends BaseController{
@Resource
AuthenticationManager authenticationManager;
@Resource
LoginManager loginManager;
@RequestMapping("login")
@ResponseBody
public JsonResult login(HttpServletRequest request,HttpServletResponse response) throws Exception{
String username=request.getParameter("username");
String password=request.getParameter("password");
IUser user=loginManager.getLoginUser(username);
if(user==null || !user.getUsername().equals(username) || !user.getPwd().equals(password.trim())){
return new JsonResult(false,"密码或用户名不正确!");
}
if(user.getTenant()==null || !MStatus.ENABLED.toString().equals(user.getTenant().getStatus())){
return new JsonResult(false,"企业机构已经被禁用!");
}
UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(username, password);
authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(token);
return new JsonResult(true,"Login Success");
}
}
以上特别说明一下,是登录的时候,进行了用户所在的账号的检查,以决定是否允许用户进入平台,当检查用户存在,并且密码也正确,然后产生一个带用户或及密码的令牌给Spring Security框架,让它通过后续的登录验证,否则后面的其他拦截器还是会进行拦截不允许用户访问。
3.2. 资源访问控制
3.2.1. 系统资源存储
平台上展示出来的页面、数据、按钮、后台允许访问的业务逻辑我们统一称之为系统资源,这些系统资源我们需要进行授权访问,以保证系统的安全性。那么我们如何来管理这些资源,这就需要我们进行系统的资源访问控制的设计。平台上采用Spring MVC作为前端的控制框架,前端借用MiniUI来进行展示,因此,我们系统上的各种资源均可以用URL来表示,如下所示:
这些菜单下若有对应具体的功能及数据时,即带有具体的URL,这些URL对应的可以是具体的数据、也可以是操作按钮。以上图所示,系统中的资源包括:
- 菜单
- 按钮
- 页面或数据
- 子系统
【说明】
平台上除了具体的数据管理以外,其他的配置均可以在菜单管理中完成,包括管理机构下的非SaaS菜单以下SaaS菜单的管理。我们均把这些资源的数据存于SysMenu表中,以实现系统的资源的统一管理,同时为了兼顾系统的菜单展示模式,我们对菜单进行了树型的管理。
【子系统表结构】
【系统菜单资源表】
3.2.2. 基于Spring Security上的扩展点:
用户登录后,需要对用户访问的资源进行安全拦截认证,我们通过在Spring Security的Filter Chain中增加我们的Filter,如下代码中的红色部分显示,在我们的Filter中,实现以下的功能要求即可:
<security:http entry-point-ref="authenticationProcessingFilterEntryPoint">
<security:intercept-url pattern="/login.do" access="ROLE_ANONYMOUS"/>
<security:intercept-url pattern="/register.do" access="ROLE_ANONYMOUS"/>
<security:intercept-url pattern="/captcha-image.do" access="ROLE_ANONYMOUS"/>
<security:intercept-url pattern="/pub/**" access="ROLE_ANONYMOUS"/>
<security:intercept-url pattern="/**" access="ROLE_PUB" />
<security:logout invalidate-session="true" logout-success-url="/login.jsp" logout-url="/j_spring_security_logout"/>
<!--security:remember-me token-validity-seconds="3600" /-->
<security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="securityInterceptorFilter" />
<security:custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" />
</security:http>
<bean id="securityInterceptorFilter" class="com.redxun.saweb.filter.SecurityInterceptorFilter">
<property name="securityDataProvider" ref=