一、spring security的过滤器
spring security 能够作为安全框架,实际上其权限拦截等功能都是基于filter实现的,下面我们讲讲security的部分过滤器
- SecurityContextPersistenceFilter:第一个起作用的认证过滤器。用途一:执行其他过滤器前判断用户的session(
HttpSession
)是否存在一个SecurityContext的上下文,如果存在则拿出来并放到SecurityContextHolder中供其他组件使用,不存在则创建SecurityContext并放入。用途二:所有过滤器执行完毕后清空SecurityContextHolder中的内容,因为SecurityContextHolder是基于ThreadLocal的,操作结束没有清空ThreadLocal会受线程机制的影响。 - LogoutFilter:处理注销请求,用户发起注销时销毁用户session、清空SecurityContextHolder,并重定向到注销成功页面。
- AbstractAuthenticationProcessingFilter:处理form登录的过滤器,与form有关所有操作都在此进行。
- DefaultLoginPageGeneratingFilter:用来生成默认的登录页面。
- BasicAuthentiactionFilter、UsernamePasswordAuthenticationFilter:主要用于做basic、登录验证等身份验证处理。
- SecurityContextHolderAwareRequestFilter:用来包装客户请求,可以在原来请求基础上为后续提供数据
- RememberMeAuthenticationFilter:依赖cookie实现用户可选择的remember(记住我)功能
- AnonymousAuthenticationFilter:实现对匿名身份处理
- ExceptionTranslationFilter:获取spring security 抛出的异常信息
- SessionManagementFilter:防止会话伪造攻击
上面所有filter通过FilterChainProxy 结合到spring security,FilterChainProxy 会按照顺序调用filter来验证各种功能。
说明:ThreadLocal存放的东西是线程内共享的,线程间互斥的。主要为了线程内通信不用进行参数传递,从而避免一些线程并发问题。ThreadLocal内部使用了自定义的ThreadLocalMap用来存放ThreadLocal信息,该ThreadLocalMap的key为ThreadLocal对象(Thread.currentThread()),value为要共享的数据。
二、spring security 使用过滤器
上面我们只是简单提到 spring security 使用 FilterChainProxy 来执行所有定义的 Filter,下面具体讲:
1、spring security的入口filter为springSecurityFilterChain。在使用web.xml 配置的项目中,使用spring security的话需要添加如下配置:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
通过配置可以知道,我们使用到了一个DelegatingFilterProxy(一个继承GenericFilterBean
的Filter)类,该类没有实际的过滤逻辑,只去寻找<filter-name>节点匹配的springSecurityFilterChain,并将过滤工作委托给springSecurityFilterChain来处理。
在springboot项目中,还记得我们之前在 WebSecurityConfigurerAdapter 的实现类 MySecurityConfig 上添加了一个 @EnableWebSecurity
注解吗。该注解会加载
WebSecurityConfiguration 类(可以从源码查找),而 WebSecurityConfiguration 类中有一个方法就是创建该 springSecurityFilterChain(Filter)。
2、springSecurityFilterChain:其实就是 FilterChainProxy(org.springframework.security.web.FilterChainProxy
),即DelegatingFilterProxy 最终会把过滤工作委托给 FilterChainProxy,该类定义如下:
public class FilterChainProxy extends GenericFilterBean {
private List<SecurityFilterChain> filterChains;//
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
doFilterInternal(request, response, chain);
}
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
}
该类中很多地方有对 SecurityFilterChain 的操作,其实 SecurityFilterChain 就是所有 spring security 定义的 Filter 存放的位置。具体SecurityFilterChain 的产生可以查看 springSecurityFilterChain 产生的方法。
三、spring security的6个核心组件
1、SecurityContext:安全上下文,用户通过Spring Security 的校验之后,验证信息存储在SecurityContext中,实际上其主要作用就是获取Authentication
对象。SecurityContext的接口定义如下:
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
2、SecurityContextHolder:主要用于存放 SecurityContext 实例,所有请求会经过过滤器 SecurityContextPersistenceFilter,该过滤器会从请求的httpsession中获取安全上下文SecurityContext并放入SecurityContextHolder,在请求结束后会清除SecurityContextHolder。SecurityContextHolder可以指定SecurityContext的存储策略:
- MODE_THREADLOCAL:将SecurityContext 存储在当前线程中,默认使用的策略。
- MODE_INHERITABLETHREADLOCAL:将SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
- MODE_GLOBAL:SecurityContext 在所有线程中都相同。
spring security存储当前认证信息的方法为:
SecurityContextHolder.getContext().setAuthentication(token);
3、Authentication:身份认证信息,含有用户信息,接口定义如下:
public interface Authentication extends Principal, Serializable {
//获取到的是用户的角色信息
Collection<? extends GrantedAuthority> getAuthorities();
//获取证明用户认证的信息,一般为密码等信息。
Object getCredentials();
// 用户的其他信息
Object getDetails();
//获取用户身份信息,未认证时获取的是用户名,已认证时获取的是 UserDetails
Object getPrincipal();
//获取当前 Authentication 是否已认证。
boolean isAuthenticated();
//设置当前 Authentication 是否已认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
4、AuthenticationManager:只有一个接收Authentication参数的authenticate方法,主要用于校验Authentication,如果失败会抛出AuthenticationException实现类(BadCredentialsException、DisabledException等)对应的异常。,其接口定义如下:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
5、Userdetails :存储用户信息,其接口定义如下:
public interface UserDetails extends Serializable {
//获取用户的角色信息。
Collection<? extends GrantedAuthority> getAuthorities();
//获取密码
String getPassword();
//获取用户名
String getUsername();
//账户是否过期
boolean isAccountNonExpired();
//账户是否被锁定
boolean isAccountNonLocked();
//证书是否过期
boolean isCredentialsNonExpired();
//是否可用
boolean isEnabled();
}
6、UserDetailsService:该接口只有一个 loadUserByUsername(String name) 方法用来获取UserDetails。用户数据保存在数据库的时候我们就需要实现该类,从数据库获取用户的身份以及角色等信息,并设置到UserDetails的实现类(org.springframework.security.core.userdetails.User
),再返回。loadUserByUsername通过数据库获取用户信息的时候,若用户不存在一定要抛出(org.springframework.security.core.userdetails.UsernameNotFoundException
)异常。
四、其他知识点:
1、权限缓存:spring security通过CachingUserDetailsService来进行缓存,其实现UserDetailsService接口,spring security加载UserDetails时先从缓存获取,没有才去持久的UserDetailsService实现类获取,再将UserDetails放到缓存。
2、security 自定义决策管理器:AbstractAccessDecisionManager,其核心方法是supports方法,该方法含有对AccessDecisionVoter投票器的操作,有无权限访问最终就是由投票器决定的(投票器 RoleVoter.java的vote方法)
3、security的优缺点:
- 优点:提供一套安全框架、提供很多用户认证功能减少开发量、易于继承到spring
- 缺点:RBAC(Role-Based access Control)不明显、大数据量下不可用、没有可操作的管理界面