文章目录
前言
Spring Security是一个当前流行的安全框架,它不仅实现了访问资源(crud)权限的控制,还可以基于它可以实现单点登陆认证业务.
本文主要介绍Spring Security的概念,认证及权限控制原理,以及单点登录的实现.
一、Spring Security的概念
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架(简单说是对访问权限进行控制的一个框架)。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
二、Spring Security的实现原理
Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。通过这些过滤器拦截进入请求,判断是否已经登录认证且具访问对应请求的权限。
Spring Security 执行流程图:
要完成访问控制,Spring Security至少需要四个拦截器(调度器、认证管理器、权限资源关联器、访问决策器)进行配合完成:
SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。
1.过滤器链的各个进行说明:
-
WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
-
SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
-
HeaderWriterFilter:用于将头信息加入响应中。
-
CsrfFilter:用于处理跨站请求伪造。
-
LogoutFilter:用于处理退出登录。
-
UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。
-
DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
-
BasicAuthenticationFilter:检测和处理 http basic 认证。
-
RequestCacheAwareFilter:用来处理请求的缓存。
-
SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。
-
AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。
-
SessionManagementFilter:管理 session 的过滤器
-
ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。
-
FilterSecurityInterceptor:可以看做过滤器链的出口。
-
RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
2.简易流程概述:
- 客户端发起一个请求,进入 Security 过滤器链。
- 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
- 当到 UsernamePasswordAuthenticationFilter*( 该对象是 Authentication 接口的实现类,该类有两个构造器,一个用于封装前端请求传入的未认证的用户信息,一个用于封装认证成功后的用户信息) 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
- 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。
3. 具体认证流程
-
用户发起登录请求,通过Security 过滤器链,到达UsernamePasswordAuthenticationFilter 过滤器.
-
UsernamePasswordAuthenticationFilter过滤器的 attemptAuthentication() 方法将未认证的 Authentication 对象传入ProviderManager 类的 authenticate() 方法进行身份认证。
ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接口,也是认证的入口。在实际开发中,我们可能有多种不同的认证方式,例如:用户名+ 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。
-
AuthenticationManager接口的常用实现类 ProviderManager 内部会维护一个 List 列表,存放多种认证方式,实际上这是委托者模式 (Delegate)的应用。每种认证方式对应着一个 AuthenticationProvider.
-
AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托对应的 AuthenticationProvider(实际上使用的是他的实现类DaoAuthenticationProvider) 进行用户认证。认证时需要UserDetailsService接口的实现类来传递用户信息.
-
自定义类XxUserDetailService实现UserDetailsService接口和其loadUserByUsername方法,这个方法根据用户输入的用户名,从数据库里面(常用微服务远程访问的方式)获取该用户的所有权限信息(统称用户信息)。MyUserDetailService拿到用户信息后,传给AuthenticationProvider(实际上使用的是他的实现类DaoAuthenticationProvider) 对比用户的密码(即验证用户),如果通过了,那么相当于通过了AuthenticationProcessingFilter拦截器,也就是登录验证通过。
三 、springsecurity使用redis实现单点登录
- 创建web安全配置类
这个类是单体项目中经常使用到的类,但现在登录走的是Oauth2的流程,因此它只需要负责创建一些对象以及配置一下放行和认证规则就行,放行loginController下的所有方法是因为登录不需要被拦截。
/**
* Security 配置类
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 初始化密码编码器,用BCryptPasswordEncoder加密密码
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 初始化认证管理对象,密码模式需要
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 放行和认证规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// 放行的请求
.antMatchers("/loginController/**").permitAll()
.and()
.authorizeRequests()
// 其他请求必须认证才能访问
.anyRequest().authenticated