Spring Security认证、授权、会话原理分析

1.结构总览

Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过滤器链结构图:

在这里插入图片描述

FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理,下图是FilterChainProxy相关类的UML图示

在这里插入图片描述

UsernamePasswordAuthenticationFilter:用于处理认证,必须提交用户名和密码封装成Authentication类,但真正处理的是AuthenticationManager。 FilterSecurityInterceptor:用于处理授权,保护web资源,真正处理是AccessDecisionManager。


2.springsecurity认证

根据上面的UML图可以得出springsecurity核心就是责任链模式下的一个个过滤器,其中对认证起关键作用的就是AuthenticationManager认证管理器,下面是整个认证流程:

在这里插入图片描述

  1. UsernamePasswordAuthenticationFilter:将用户名密码封装成Authentication对象,然后传递给AuthenticationManager(关键认证处理器);
  2. AuthenticationManager:(实现类:providerManager):交由AuthenticationProvider认证authenticate();
  3. AuthenticationProvider:(实现类:DaoAuthenticationProvider):用于委托认证authenticate(),调用UserDetailsService去获取信息;
  4. UserDeatilsService:(实现类:InMemoryUserDetailsManager内存认证等):根据不同的存储媒介如内存或数据库去拿到用户信息;
  5. PasswordEncoder:(实现类:NoOpPasswordEncoder等):用于密码比对,可以选择直接字符串比较或者加密比较处理;
  6. SecurityContextHolder:上下文对象用于保存会话;

这就是完整的一个认证,我们可以根据用不同实现类引入容器来实现我们想要的处理逻辑,如从内存或数据库读取数据,选择对应的UserDetailsService实现类即可。

@Bean
public UserDetailsService userDetailsService() {
	InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
	manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
	manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
	return manager;
}

3.springsecurity授权

springsecurity授权拦截如下:

在这里插入图片描述

  1. FilterSecurityInterceptor:用于接受已认证用户请求资源拦截,然后交由SecurityMetadataSource处理。
  2. Securitymeatadatasource:(实现类:DefaultFilterInvocationSecurityMetadataSource)用于获取访问当前资源的权限集合。
  3. AccessDecisionManager:(Spring Security内置了三个基于投票的AccessDecisionManager实现类如下,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased)采用投票的方式来确定是否能够访问受保护资源。
  4. AccessDecisionVoter:(实现类如RoleVoter、AuthenticatedVoter和WebExpressionVoter等)不同的投票者决定不同投票方式。

授权的方式包括 web授权和方法授权,web授权是通过url拦截进行授权,方法授权是通过方法拦截进行授权。他们都会调用accessDecisionManager进行授权决策,若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时通过web授权和方法授权则先执行web授权,再执行方 法授权,最后决策通过,则允许访问资源,否则将禁止访问。

3.1.web授权

通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的URL,如下代码:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
	.authorizeRequests() (1)
	.antMatchers("/r/r1").hasAuthority("p1") (2)
	.antMatchers("/r/r2").hasAuthority("p2") (3)
	.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')") (4)
	.antMatchers("/r/**").authenticated() (5)
	.anyRequest().permitAll() (6)
	.and()
	.formLogin()
// ...
}

(1)http.authorizeRequests() 方法有多个子节点,每个macher按照他们的声明顺序执行。 (2)指定"/r/r1"URL,拥有p1权限能够访问 (3)指定"/r/r2"URL,拥有p2权限能够访问 (4)指定了"/r/r3"URL,同时拥有p1和p2权限才能够访问 (5)指定了除了r1、r2、r3之外"/r/x "资源,同时通过身份认证就能够访问,这里使用SpEL(Spring Expression Language)表达式。。 (6)剩余的尚未匹配的资源,不做保护。

3.2.方法授权

从Spring Security2.0版本开始,它支持服务层方法的安全性的支持。@PreAuthorize,@PostAuthorize, @Secured三类注解。 我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。


4.会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管 理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份,SpringSecurity获取当前登录用户信息的方法为SecurityContextHolder.getContext().getAuthentication()

4.1会话控制

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管 理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份,SpringSecurity获取当前登录用户信息的方法为SecurityContextHolder.getContext().getAuthentication()

机制描述
always如果需要就创建一个Session(默认)登录时
ifRequired如果需要就创建一个Session(默认)登录时
neverSpringSecurity 将不会创建Session,但是如果应用中其他地方创建了Session,那么SpringSecurity将会使用它。
statelessSpringSecurity将绝对不会创建Session,也不使用Session

通过以下配置方式对该选项进行配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.sessionManagement()
	.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}

默认情况下,Spring Security会为每个登录成功的用户会新建一个Session,就是ifRequired 。 若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了 session,那么Spring Security会用它的。 若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建 session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API 及其无状态认证机制。


总结:

SpringSecurity认证是通过UsernamePasswordAuthenticationFilter拦截器调用AuthenticationManager认证管理器来进行后续认证处理,我们可以在后续一环中进行对应接口实现来引入自己的代码逻辑。

SpringSecurity授权是通过FilterSecurityInterceptor(web方式授权)或MethodSecurityInterceptor(方法方式授权) 调用accessDecisionManager来进行授权处理。

SpringSecurity会话有两种方式一种是传统session对象,储存在SecurityContextHolder上下文对象,一种是JWT token的方式。

end
  • 作者:Endwas(联系作者)
  • 发表时间:2021-02-01 13:46
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者名字和博客地址
  • 评论

    aaa
    现在基本都用springsecurity Oauth2吧?
    Endwas
    博主
     @ aaa
    对,现在主流分布式系统都用OAuth2的