第六章 SpringSecurity-原理
6.1 认证原理-过滤器链的调用
1 源码调试分析
- 程序入口
- 打断点-第一批次
- 运行调试
- 打断点-关键点
2 过滤器
<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> |
3 debug走起…
3.1 初始化方法
3.2 过滤器:功能扩展的多个过滤器->责任链设计模式
3.3 获取过滤器链中的过滤器,封装为虚拟的VirtualFilterChain对象,并开始执行过滤
3.4 开始一个一个的执行过滤器
3.5 自定义过滤器
6.2 认证原理-相关过滤器解释
1.SecurityContextPersistenceFilter
过滤器链头,是从 SecurityContextRepository 中取出用户认证信息,默认实现为 HttpSessionSecurityContextRepository,它会从 Session 中取出已认证的用户信息,提高效率,避免每次请求都要查询用户认证信息 取出之后会放入 SecurityContextHolder 中,以便其它 filter 使用,SecurityContextHolder 使用 ThreadLocal(一个threadLocal只能绑定一个数据多个新建多个ThreadLocal) (其中绑定当前线程(有个map)只能给当前线程用)存储用户认证信息,保证线程之间信息隔离,最后再 finally 中清除该信息(多例解决线程安全问题) |
2.WebAsyncManagerIntegrationFilter
提供了对 SecurityContext 和 WebAsyncManager 的集成,会把 SecurityContext 设置到异步线程,使其也能获取到用户上下文认证信息 |
3.HeaderWriterFilter
会往请求的 Header 中添加相应的信息 响应头: 防止 请求头: |
CsrfFilter
跨域请求伪造过滤器,通过客户端穿来的 token 与服务端存储的 token 进行对比来判断请求 |
4.LogoutFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
匹配URL,默认为 /logout,匹配成功后则会用户退出,清除认证信息,若有自己的退出逻辑,该过滤器可以关闭 |
5.UsernamePasswordAuthenticationFilter
登录认证过滤器,默认是对 /login 的 POST 请求进行认证,首先该方法会调用 attemptAuthentication 尝试认证获取一个 Authentication 认证对象,然后通过 sessionStrategy.onAuthentication 执行持久化,也就是保存认证信息,然后转向下一个 Filter,最后调用 successfulAuthentication 执行认证后事件 attemptAuthentication 该方法是认证的主要方法,认证基本流程为 UserDeatilService 根据用户名获取到用户信息,然后通过 UserDetailsChecker.check 对用户状态进行校验,最后通过 additionalAuthenticationChecks 方法对用户密码进行校验完后认证后,返回一个认证对象 |
6.DefaultLoginPageGeneratingFilter
当请求为登录请求时,生成简单的登录页面,可以关闭 |
7.BasicAuthenticationFilter
Http Basic 认证的支持,该认证会把用户名密码使用 base64 编码后放入 header 中传输,认证成功后会把用户信息放入 SecurityContextHolder 中 |
8.RequestCacheAwareFilter
恢复被打断时的请求 |
9.SecurityContextHolderAwareRequestFilter
针对 Servlet api 不同版本做一些包装 |
10.AnonymousAuthenticationFilter
SecurityContextHolder 中认证信息为空,则会创建一个匿名用户到 SecurityContextHolder 中 |
11.SessionManagementFilter
与登录认证拦截时作用一样,持久化用户登录信息,可以保存到 Session 中,也可以保存到 cookie 或 redis 中 |
12.ExceptionTranslationFilter
异常拦截,处于 Filter 链条后部,只能拦截其后面的节点并着重处理 AuthenticationException(认证异常) 与 AccessDeniedException(请求被拒绝异常) 异常 |
6.3 认证原理-重点UsernamePasswordAuthenticationFilter
6.3.1 UsernamePasswordAuthencationFilter源码流程-不带图
1、获取到页面的用户名和密码 2、将username和password包装成UsernamePasswordAuthenticationToken 3、获取系统的认证管理器(AuthenticationManager)来调用authenticate方法完成认证 3.1)AuthenticationManager获取ProviderManager来调用ProviderManager.authenticate() 3.2)ProviderManager获取到所有的AuthenticationProvider判断当前的提供者能否支持,如果支持provider.authenticate(authentication); 现在我们DaoAuthenticationProvider( authentication :页面封装用户名和密码的对象) 3.2.1)retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); 3.2.1.1)loadedUser = this.getUserDetailsService().loadUserByUsername(username); (调用我们自己的UserDetailsService来去数据库查用户,按照用户名查出来的用户的详细信息)封装成UserDetails 3.2.2)preAuthenticationChecks.check(user);(预检查,账号是否被锁定等…) 3.2.3)additionalAuthenticationChecks(附加的认证检查) 3.2.3.1)使用passwordEncoder. matches检查页面的密码和数据库的密码是否一致 3.2.4)postAuthenticationChecks.check(user);(后置认证,检查密码是否过期) 3.2.5)createSuccessAuthentication:将认证成功信息重新封装成UsernamePasswordAuthenticationToken 3.3)3.2又返回了一个新的UsernamePasswordAuthenticationToken,然后擦掉密码 4、 eventPublisher.publishAuthenticationSuccess(result);告诉所有监听器认证成功了 |
6.3.2 UsernamePasswordAuthencationFilter源码流程-带图
1 执行过滤器,获取到页面的用户名和密码
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
2 将username和password包装成UsernamePasswordAuthenticationToken
3 获取系统的认证管理器(AuthenticationManager)来调用authenticate方法完成认证(this.getAuthenticationManager().authenticate(authRequest))
3.1)、 AuthenticationManager获取ProviderManager来调用ProviderManager.authenticate()
3.2)、 ProviderManager获取到所有的AuthenticationProvider判断当前的提供者能否支持,如果支持provider.authenticate(authentication);
DaoAuthenticationProvider( authentication :页面封装用户名和密码的对象)
3.2.1)、retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
3.2.1.1)、 loadedUser = this.getUserDetailsService().loadUserByUsername(username);
(调用我们自己的UserDetailsService来去数据库查用户,按照用户名查出来的用户的详细信息)封装成UserDetail
3.2.2)、 preAuthenticationChecks.check(user);(预检查,账号是否被锁定等…)
3.2.3)、 additionalAuthenticationChecks(附加的认证检查)
3.2.3.1)、使用passwordEncoder. matches检查页面的密码和数据库的密码是否一致
3.2.4)、 postAuthenticationChecks.check(user);(后置认证,检查密码是否过期)
3.2.5)、 createSuccessAuthentication:将认证成功的信息重新封装成UsernamePasswordAuthenticationToken
3.3)、 3.2又返回了一个新的UsernamePasswordAuthenticationToken,然后擦掉密码
4 eventPublisher.publishAuthenticationSuccess(result);认证成功
5 successfulAuthentication(request, response, chain, authResult);
通过调用 SecurityContextHolder.getContext().setAuthentication(...) 将 Authentication 对象赋给当前的 SecurityContext
org.springframework.security.core.context.SecurityContextHolderStrategy
ThreadLocal线程数据绑定
SecurityContextHolder.getContext();就能获取到之前认证好的Authentication对象(UsernamePasswordAuthenticationToken)
6.3.3 断点参考
6.4 认证原理-流程及相关类(API)
认证&授权
6.4.1 认证流程
- 用户使用用户名和密码登录
- 用户名密码被过滤器(默认为 UsernamePasswordAuthenticationFilter)获取到,封装成 Authentication(UsernamePasswordAuthenticationToken)
- token(Authentication实现类)传递给 AuthenticationManager 进行认证
- AuthenticationManager 认证成功后返回一个封装了用户权限信息的 Authentication 对象
- 通过调用 SecurityContextHolder.getContext().setAuthentication(...) 将 Authentication 对象赋给当前的 SecurityContext
- 将用户的信息保存到当前线程上,共享起来
- SecurityContextHolder.getContext();就能获取到之前认证好的Authentication对象(UsernamePasswordAuthenticationToken)
6.4.2 默认执行顺序
1 UsernamePasswordAuthenticationFilter
|
2 DaoAuthenticationProvider(实现了 AuthenticationProvider)
通过 UserDetailsService 获取 UserDetails
将 UserDetails 和 UsernamePasswordAuthenticationToken 进行认证匹配用户名密码是否正确
若正确则通过 UserDetails 中用户的权限、用户名等信息生成新的 Authentication 认证对象并返回
6.4.3 相关类
1.WebSecurityConfigurerAdapter(配置使用)
为创建 WebSecurityConfigurer 实例提供方便的基类,重写它的 configure 方法来设置安全细节 configure(HttpSecurity http):重写该方法,通过 http 对象的 authorizeRequests()方法定义URL访问权限,默认会为 formLogin() 提供一个简单的测试HTML页面 configure (AuthenticationManagerBuilder auth):通过 auth 对象的方法添加身份验证 |
2.SecurityContextHolder
SecurityContextHolder 用于存储安全上下文信息(如操作用户是谁、用户是否被认证、用户权限有哪些),它用 ThreadLocal 来保存 SecurityContext,者意味着 Spring Security 在用户登录时自动绑定到当前现场,用户退出时,自动清除当前线程认证信息,SecurityContext 中含有正在访问系统用户的详细信息 |
3.AuthenticationManagerBuilder
用于构建认证 AuthenticationManager 认证,允许快速构建内存认证、LDAP身份认证、JDBC身份验证,添加 userDetailsService(获取认证信息数据) 和 AuthenticationProvider's(定义认证方式) |
4.UserDetailsService
该接口仅有一个方法 loadUserByUsername,Spring Security 通过该方法获取用户信息 |
5.UserDetails
代表了Spring Security的用户实体类,带有用户名、密码、权限特性等性质,可以自己实现该接口,供 Spring Security 安全认证使用,Spring Security 默认使用的是内置的 User 类 将从数据库获取的 User 对象传入实现该接口的类,并获取 User 对象的值来让类实现该接口的方法 通过 Authentication.getPrincipal() 的返回类型是 Object,但很多情况下其返回的其实是一个 UserDetails 的实例 |
6.Authentication
Authentication 是一个接口,用来表示用户认证信息,在用户登录认证之前相关信息(用户传过来的用户名密码)会封装为一个 Authentication 具体实现类对象,默认情况下为 UsernamePasswordAuthenticationToken,登录之后(通过AuthenticationManager认证)会生成一个更详细的、包含权限的对象,然后把它保存在权限线程本地的 SecurityContext 中,供后续权限鉴定用 Authentication.principal可以获取到已经认证的用户详细信息 UsernamePasswordAuthenticationToken (密码被擦除,authenticated=true) |
7.GrantedAuthority
GrantedAuthority 是一个接口,它定义了一个 getAuthorities() 方法返回当前 Authentication 对象的拥有权限字符串,用户有权限是一个 GrantedAuthority 数组,每一个 GrantedAuthority 对象代表一种用户权限 |
8.AuthenticationManager
AuthenticationManager 是用来处理认证请求的接口,它只有一个方法 authenticate(),该方法接收一个 Authentication 作为对象,如果认证成功则返回一个封装了当前用户权限信息的 Authentication 对象进行返回 它默认的实现是 ProviderManager,但它不处理认证请求,而是将委托给 AuthenticationProvider 列表,然后依次使用 AuthenticationProvider 进行认证,如果有一个 AuthenticationProvider 认证的结果不为null,则表示成功(否则失败,抛出 ProviderNotFoundException),之后不在进行其它 AuthenticationProvider 认证,并作为结果保存在 ProviderManager 认证校验时最常用的方式就是通过用户名加载 UserDetails,然后比较 UserDetails 密码与请求认证是否一致,一致则通过,Security 内部的 DaoAuthenticationProvider 就是使用这种方式 认证成功后加载 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中 |
9.AuthenticationProvider
AuthenticationProvider 是一个身份认证接口,实现该接口来定制自己的认证方式,可通过 UserDetailsSevice 对获取数据库中的数据 该接口中有两个需要实现的方法: Authentication authenticate(Authentication authentication):认证处理,返回一个 Authentication 的实现类则代表成功,返回 null 则为认证失败 supports(Class<?> aClass):如果该 AuthenticationProvider 支持传入的 Authentication 认证对象,则返回 true ,如:return aClass.equals(UsernamePasswordAuthenticationToken.class); |
10.AuthorityUtils
是一个工具包,用于操作 GrantedAuthority 集合的实用方法: commaSeparatedStringToAuthorityList(String authorityString):从逗号分隔符中创建 GrantedAuthority 对象数组,帮我们快速创建出权限的集合 |
11.AbstractAuthenticationProcessingFilter
该抽象类继承了 GenericFilterBean,是处理 form 登录的过滤器,与 form 登录相关的所有操作都在该抽象类的子类中进行(UsernamePasswordAuthenticationFilter 为其子类),比如获取表单中的用户名、密码,然后进行认证等操作 该类在 doFilter 方法中通过 attemptAuthentication() 方法进行用户信息逻辑认证,认证成功会将用户信息设置到 Session 中 |
12.HttpSecurity
用于配置全局 Http 请求的权限控制规则,对哪些请求进行验证、不验证等 常用方法: authorizeRequests():返回一个配置对象用于配置请求的访问限制 formLogin():返回表单配置对象,当什么都不指定时会提供一个默认的,如配置登录请求,还有登录成功页面 logout():返回登出配置对象,可通过logoutUrl设置退出url antMatchers:匹配请求路径或请求动作类型,如:.antMatchers("/admin/**") addFilterBefore: 在某过滤器之前添加 filter addFilterAfter:在某过滤器之后添加 filter addFilterAt:在某过滤器相同位置添加 filter,不会覆盖相同位置的 filter hasRole:结合 antMatchers 一起使用,设置请求允许访问的角色权限或IP,如: .antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
|
13.PasswordEncoder
Spring 提供的一个用于对密码加密的接口,首选实现类为 BCryptPasswordEncoder |
14.BCryptPasswordEncoder
spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥 对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。 (1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。 (2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。 这正是为什么处理密码时要用hash算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码 |
6.5 授权原理-AOP-MethodSecurityInterceptor
MethodSecurityInterceptor(基于AOP模式进行权限检查)
* 授权(权限检查机制)采用AOP机制:
org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
* 方法执行前org.springframework.security.access.AccessDecisionManager通过投票机制决定这个方法是否可以被执行
6.5.1 例如
@PreAuthorize(value="hasRole('学徒') AND hasAuthority('luohan')") @GetMapping("/level1/1") public String leve1Page1(){ return "/level1/1"; } |
6.5.2 拦截器invoke方法
6.5.3 支持各种功能的投票器
6.5.4 投票器标识
6.5.5 AccessDecisionManager利用系统中AccessDecisionVoter(投票器)进行授权操作:
1)AffirmativeBased:有一个拒绝都不行
2)ConsensusBased:赞成票数大于拒绝即可
3)UnanimousBased:至少有一个赞成,不能全弃权和任何一个拒绝