最近开始准备做一个项目,找了慕课的权限管理系统,开始学习spring seurity框架,这里记录一下这个框架的架构
是什么
一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。
简单来说:Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization)。Spring Security在架构上将认证与授权分离,并提供了扩展点。
核心对象
首先我们看一下Spring Security中的三个核心对象SecurityContextHolder, SecurityContext 和 Authentication
SecurityContextHolder是SecurityContext的存放容器,使用ThreadLocal存储,意味着SecurityContext在相同的线程中的方法都是可用的。
SecurityContext主要的存储应用的principal信息,然后在Spring Security中用 Authentication来表示。
这里贴上获取principal的方式
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
然后我们看一下对于Authentication的定义
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); //通常是密码
Object getCredentials(); //更多细节信息来标识用户,可能是ip地址
Object getDetails(); //标识是否已经认证,如果是用户密码登陆则这里通常是用户名
Object getPrincipal(); //是否认证
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
一个常见的认证过程通常是这样的,创建一个UsernamePasswordAuthenticationToken,然后交给authenticationManager认证,认证通过SecurityContextHolder存放Authentication的信息
UserDetails与UserDetailService
UserDetails是Spring Security里的一个关键接口,他表示一个principal
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetails 提供了认证所需的必要信息,在实际使用里,可以自己实现UserDetails,并增加额外的信息,比如email、mobile等信息。
在Authentication中的principal的通常是用户名,我们可以通过UserDetailService获取UserDetails;
小结
-
SecurityContextHolder, 用来访问 SecurityContext.
-
SecurityContext, 用来存储Authentication .
-
Authentication, 代表凭证.
-
GrantedAuthority, 代表权限.
-
UserDetails, 用户信息.
-
UserDetailsService,获取用户信息.
web security的实现方式
Web层中的Spring Security(用于UI和HTTP后端)基于Servlet Filters,下图显示了单个HTTP请求的处理程序的典型分层。
Spring Security通过FilterChainProxy作为单一的Filter注册到web层,Proxy内部的Filter。
FilterChainProxy相当于一个filter的容器,通过VirtualFilterChain来依次调用各个内部filter
不同的filter拦截器
1.SecurityContextPersistenceFilter
- 位于过滤器的顶端,是起作用的第一层过滤器,在别的过滤器之前率先判断session是否存在上下文
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
- 在所有过滤器之后清空spring security内容
SecurityContextHolder.clearContext();
2.LogoutFilter 用户发送注销请求时候清空session和applicationContext,同时可以配合其他的去清空cookie
3.AbstractAuthenticationProcessingFilter 处理form登陆的过滤器
与form有关的所有登陆在此执行,判断用户名和密码然后跳转对应成功和错误
4.DefaultLoginPageGeneratingFiler 默认登陆页面(丑)
5.BaseicAuthenticationFilter 与AbstractAuthenticationProcessingFilter 类似,只是验证的方式不同
6.SecurityContextHolderAwareRequestFilter 用来包装客户的信息,用来返回对应get/set
7.RememberMeAuthenticationFilter 通过cookie免登陆
8.AnoymousAuthenticationFilter 匿名登陆
9.ExceptionTranslation 异常控制
10.SessionManagementFilter 防止攻击
11.FilterSecurityInterceptor
核心处理流程
数据库管理
- 当一个用户登陆时候,先执行身份认证,如果认证未通过,重新认证。
- 当用户认证通过,调用角色管理器判断他能否访问,这里就是用到spring security提供的UserDetails,Authentication是spring security使用的安全访问控制用户信息的安全对象,这里的UserDetails是用户的信息原
- 当我们需要使用数据库管理用户时候,我们需要手动实现UserDetailsService接口中的loadUserByUsername方法,这就需要我们在数据库中准备3张表:用户表、权限表、角色表、用户和角色关系表、权限和角色关系表。
- 这样我们就把用户登陆放到数据库中管理了
权限缓存
CachingUserDetailService类
- 这个类的构造方法接收了用于真正加载UserDetails的UserDetailsService实现类
- 当需要UserDeatils时候,会优先从缓存中获取,如果缓存中没有Details存在,就会用UserDetailsService的实现类来加载,然后存在缓存中
- Details跟缓存的交互就是通过UserCache来实现
实际项目中,会使用别的缓存,redis之类来缓存别的信息。
自定义决策
当用户通过认证,调用决策管理器AbstractAccessDecisionManager,其中有判断方法supports,其中的参数decisionVoters类型是AccessDesionvoter,他是spring security引入的投票器的概念,最终决定权就是投票器决定
RoleVoter中vote方法会循环判断是否通过,最终返回ACCESS_GRANTED
目前三种投票器:
- AffirmativeBased(一票通过)
- ConsensusBased(半数通过)
- UnanimousBased(全数通过)