在生活中常见各种系统中,若涉及到用户交互基本上都要进行权限管理,权限管理是系统安全的范畴,权限管理就是实现对用户访问系统管理,按照不同的
安全策略
与安全规则
限制用户只可访问自己被授权的资源。
权限管理包括用户身份认证与资源授权两部分,通常只有通过身份认证的用户,才可以访问该用户已经授权过的资源信息。
身份认证
:就是验证该用户是否为系统合法用户的处理过程,常见的身份验证就是核对用户输入的用户名与密码,除此之外生活中也可以见到其他验证手段:如面部识别验证、指纹识别验证、虹膜识别验证,这些不同的验证方式的目的都是用来验证用户是否为系统的合法用户。
资源授权
:即对用户访问资源进行限制,当用户通过身份验证后,还需进一步分配其访问资源权限,没有分配的资源是无法访问的。
Shiro作为一个成熟权限解决方案已经很多年了,它具有轻量级、简单使用的特点,但在微服务应用普及时代下,作为Spring 全家桶的Spring Security 拥有更好的扩展,支持热门的身份认证协议如OAuth2。
Spring Security 作为Spring 全家桶的一份子,不仅有更好的微服务先天整合优势,而且也对OAuth2、 Kerberos、SAML等认证协议有着良好的支持,大多数Java开发工程师都不是专业的WEB安全工程师,个人开发的权限管理系统可能会有大量的安全漏洞,Spring Security的强大之处就是,即使你是个安全方面的小白,只要你的系统使用了Spring Security,它就能帮助我们的系统防御很多网络攻击,如CSRF攻击等。
重复造轮子实现一个自定义权限管理系统,需要考虑大量易用性、安全性方面上的问题,不仅耗费大量人力和开发成本,而且做出来的系统可能还没有开源解决方案好。实际上除了大公司,一般都是在开源解决方案的基础上进行扩展以满足实际的使用要求。
在具体学习Spring Security框架前,有必要了解其架构,这对于我们学习其基本概念、认证以及授权思路有着非常大的帮助,下面是Spring Security 的认证与授权架构图。
在Spring Security中 认证(Authentication) 与 授权(Authorization) 是分开的,这样做的好处是两者都可以很方便集成第三方解决方案,这并不会干扰Spring Security 认证与授权流程。下面重点介绍Spring Security 认证模块关键类和接口。
Spring Security中认证功能主要是由 AuthenticationManager 接口负责的,此接口定义如下所示:
此接口只有一个authenticate方法,该方法有三种不同的返回值:
AuthenticationManager 主要实现类是ProviderManager,其实现类定义如下:
此类管理了很多AuthenticationProvider实例。Spring Security 一个完整认证流程中,可能会存在多个AuthenticationProvider实例,用来实现多种不同的认证方式(例如form表单认证、OAuth2.0认证),在AuthenticationProvider构造方法中有一个可选的parent,其作用是:当所有的AuthenticationProvider认证都失败时,就会调用此parent 进行认证。
AuthenticationProvider 类似AuthenticationManager,两者区别是:AuthenticationProvider 提供了supports 方法用来判断是否支持给定的Authentication类型,这句话不太好理解,以AuthenticationProvider接口的实现类
RemoteAuthenticationProvide为例,如下图所示:
可以看到其 supports 方法的实现如下图,isAssignableFrom 方法的作用是:用来判断当前Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其接口或者父类(与instance of 作用恰好相反)。这里则是判断方法入参authentication对象是否是UsernamePasswordAuthenticationToken 类本身或者UsernamePasswordAuthenticationToken父类,这也就是我们上面提到的判断是否支持给定的Authentication类型的具体含义的实现。
Spring Security 中用户认证信息主要由Authentication接口的实现类进行保存,此接口重要方法定义如下:
方法名称 | 解释说明 | |
---|---|---|
getAuthorities | 用来获取用户的权限信息 | |
getCredentials | 用来获取用户凭证,一般指的是密码 | |
getDetails | 获取用户携带的详细信息 | |
getPrincipal | 用来获取当前用户,可能是用户名或用户对象 | |
isAuthenticated | 用来表明当前用户是否认证成功 |
Spring Security 将用户登陆成功的用户信息保存在session中,为了能够更好的获取到用户信息,Spring Security 提供了一个SecurityContextHolder类,用来保存用户登陆信息。
具体原理是:当用户登陆成功后,Spring Security会将用户信息保存在SecurityContextHolder中,SecurityContextHolder中的数据保存默认是通过ThreadLocal来实现的,ThreadLocal这个类想必大家都不陌生,我们经常会使用到它来保存一些线程隔离的、全局的变量信息。使用ThreadLocal维护变量时,每个线程都会获得该线程独享一份变量副本。这里通过ThreadLocal 实现了请求线程与用户数据绑定(每一个请求线程都有自己的用户数据),当登陆请求处理完毕后,Spring Security会将SecurityContextHolder中保存用户信息取出并保存在Session中,同时删除SecurityContextHolder保存的用户信息,待下一次请求时,再从session中取出用户信息并保存在SecurityContextHolder中,然后在请求结束时,又会将用户信息从SecurityContextHolder取出并保存在session中并清除SecurityContextHolder中的用户信息。这样做的好处就是可以很方便在Controller或者service中随时获取到用户信息(通过SecurityContextHolder),但是在子线程中获取父线程用户数据就异常麻烦。
当用户认证完成后,下一步就是资源授权了。在授权体系中有三个比较重要的接口AccessDecisionManager、AccessDecisionVoter 、ConfigAttribute。
AccessDecisionManager是授权体系中的决策器,通过其decide方法判断用户当前的访问是否被允许。
AccessDecisionManager 与AccessDecisionVoter 共同决定了用户是否有权对资源进行访问。两者都有很多个实现类,两者之间的关系类似与认证体系中 AuthenticationManager与ProviderManager的关系,这一点我们可以在AccessDecisionManager 接口实现类AbstractAccessDecisionManager 找到答案:
AccessDecisionVoter 充当访问投票器的角色,AccessDecisionVoter 会检查用户是否拥有具备操作相应权限的角色,进而透出反对、赞成和弃权,其接口定义信息如下所示:
在授权流程中:AccessDecisionManager挨个遍历AccessDecisionVoter ,然后多个投票器的vote方法共同决定用户是否可以访问相应的资源。
在授权体系流程中,用户访问的一个资源(通常是一个网络url或者是Java方法)所需要的角色被包装成一个ConfigAttribute对象,此对象保存了用户授权后的角色信息,其接口定义如下:
ConfigAttribute只有一个 getAttribute 方法,此方法返回一个String字符串,就是角色名称,一般清空下此角色名称都有一个 ROLE_ 前缀。