SpringSecurity--原理(代码级别)

第六章 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.33.2又返回了一个新的UsernamePasswordAuthenticationToken,然后擦掉密码

4 eventPublisher.publishAuthenticationSuccess(result);告诉所有监听器认证成功了

6.3.2 UsernamePasswordAuthencationFilter源码流程-带图

1 执行过滤器,获取到页面的用户名和密码

public class UsernamePasswordAuthenticationFilter extends  AbstractAuthenticationProcessingFilter

2 usernamepassword包装成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 认证流程

  1. 用户使用用户名和密码登录
  2. 用户名密码被过滤器(默认为 UsernamePasswordAuthenticationFilter)获取到,封装成 Authentication(UsernamePasswordAuthenticationToken
  3. token(Authentication实现类)传递给 AuthenticationManager 进行认证
  4. AuthenticationManager 认证成功后返回一个封装了用户权限信息的 Authentication 对象
  5. 通过调用 SecurityContextHolder.getContext().setAuthentication(...)  将 Authentication 对象赋给当前的 SecurityContext
  6. 将用户的信息保存到当前线程上,共享起来
  7. SecurityContextHolder.getContext();就能获取到之前认证好的Authentication对象(UsernamePasswordAuthenticationToken)

6.4.2 默认执行顺序

1 UsernamePasswordAuthenticationFilter

  1. 用户通过url:/login 登录,该过滤器接收表单用户名密码
  2. 判断用户名密码是否为空
  3. 生成 UsernamePasswordAuthenticationToken
  4. 将 Authentiction 传给 AuthenticationManager接口的 authenticate 方法进行认证处理
  5. AuthenticationManager 默认是实现类为 ProviderManager ,ProviderManager 委托给 AuthenticationProvider 进行处理
  6. UsernamePasswordAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter 抽象类,AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中对登录成功进行了处理,通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
  7. 下次请求时,在过滤器链头的 SecurityContextPersistenceFilter 会从 Session 中取出用户信息并生成 Authentication(默认为 UsernamePasswordAuthenticationToken),并通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
  8. 需要权限才能访问的请求会从 SecurityContext 中获取用户的权限进行验证

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

该接口仅有一个方法 loadUserByUsernameSpring 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")

方法名        

用途

access(String)        

SpringEL表达式结果为true时可访问

anonymous()        

匿名可访问

denyAll()        

用户不可以访问

fullyAuthenticated()        

用户完全认证访问(非remember me下自动登录)

hasAnyAuthority(String)        

参数中任意权限可访问

hasAnyRole(String)        

参数中任意角色可访问

hasAuthority(String)        

某一权限的用户可访问

hasRole(String)        

某一角色的用户可访问

permitAll()        

所有用户可访问

rememberMe()        

允许通过remember me登录的用户访问

authenticated()        

用户登录后可访问

hasIpAddress(String)        

用户来自参数中的IP可访问

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(投票器)进行授权操作:

1AffirmativeBased:有一个拒绝都不行

2ConsensusBased:赞成票数大于拒绝即可

3UnanimousBased:至少有一个赞成,不能全弃权和任何一个拒绝

发布了227 篇原创文章 · 获赞 77 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/m2606707610/article/details/104099550