• SpringSecurity原理解析(五):HttpSecurity 类处理流程


    1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别

          通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件

          的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,

          SpringSecurity 配置文件如下所示:

    1. <security:http auto-config="true" use-expressions="true">
    2. <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
    3. <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" >security:intercept-url>
    4. <security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/home.jsp" authentication-failure-url="/error.jsp"/>
    5. <security:csrf disabled="true"/>
    6. <security:remember-me
    7. token-validity-seconds="1200"
    8. data-source-ref="dataSource"
    9. remember-me-parameter="remember-me"/>
    10. <security:access-denied-handler error-page="error.jsp"/>
    11. security:http>
    12. <bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder"/>
    13. <security:authentication-manager>
    14. <security:authentication-provider user-service-ref="userServiceImpl">
    15. <security:password-encoder ref="bCryptPasswordEncoder">security:password-encoder>
    16. security:authentication-provider>
    17. security:authentication-manager>

          也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在

          SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,通过配置类的方式来配置

          SpringSecurity。SpringSecurity配置类需要继承类 WebSecurityConfigurerAdapter,并重写

          configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 方法;

           SpringSecurity 配置类如下所示:     

    1. /**
    2. * SpringSecurity的配置文件
    3. * WebSecurityCofniguration中 @Bean注解 把 FilterChainProxy 注入到了容器中 而且名称为springSecurityFilterChain
    4. * 而 FilterChainProxy 对象是通过 WebSecurity 构建的
    5. *
    6. * @EnableWebSecurity
    7. */
    8. @Configuration
    9. @EnableWebSecurity
    10. public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
    11. @Autowired
    12. private UserService userService;
    13. @Autowired
    14. private BCryptPasswordEncoder bCryptPasswordEncoder;
    15. @Autowired
    16. private PersistentTokenRepository persistentTokenRepository;
    17. @Override
    18. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    19. auth.userDetailsService(userService)// 数据库认证,绑定需要执行用户认证操作的service
    20. .passwordEncoder(bCryptPasswordEncoder); // 设置加密处理的方式
    21. //auth.inMemoryAuthentication().withUser("root").password("123")
    22. }
    23. /**
    24. * 容器中注入 BCryptPasswordEncoder
    25. * @return
    26. */
    27. @Bean
    28. public BCryptPasswordEncoder bCryptPasswordEncoder(){
    29. return new BCryptPasswordEncoder();
    30. }
    31. /**
    32. * HttpSecurity 相当于 SpringSecurity配置文件中 http 标签
    33. *
    34. * @param http
    35. * @throws Exception
    36. */
    37. @Override
    38. protected void configure(HttpSecurity http) throws Exception {
    39. http.authorizeRequests()
    40. //匿名访问资源
    41. .antMatchers("/login.html","/css/**","/img/**")
    42. // 配置需要放过的资源,表示前边 antMatchers 配置的资源都需要放过
    43. .permitAll()
    44. .antMatchers("/**")
    45. //认证的资源及所具备的权限
    46. .hasAnyRole("USER")
    47. .anyRequest()//表示所有请求都需要认证
    48. .authenticated()//需要认证
    49. //and() 返回一个HttpSecurity
    50. .and()
    51. // 配置登录表单相关的信息
    52. .formLogin()
    53. // 指定自定义的登录页面
    54. //todo 注意:
    55. // 对于前后端分离的项目,不需要指定跳转的页面,只需要 loginProcessingUrl
    56. // 设置请求资源url就行了
    57. .loginPage("/login.html") //认证表单相关信息
    58. .loginProcessingUrl("/login") // 表单提交的登录地址
    59. .defaultSuccessUrl("/home.html")
    60. //表示上边与form表单提交得资源都要放过
    61. .permitAll()
    62. .and()
    63. .rememberMe() // 放开 记住我 的功能
    64. .tokenRepository(persistentTokenRepository) // 持久化
    65. .and()
    66. //csrf设置
    67. .csrf()
    68. .disable();
    69. HttpSecurity http1 = http.authorizeRequests()
    70. // 配置需要放过的资源
    71. .antMatchers("/login.html", "/css/**", "/img/**")
    72. .permitAll()//表示放过前边 antMatchers 配置得资源
    73. .antMatchers("/**")
    74. .hasAnyRole("USER")
    75. .anyRequest()
    76. .authenticated()
    77. .and();
    78. }
    79. /**
    80. * 向Spring容器中注入 PersistentTokenRepository 对象
    81. * @param dataSource
    82. * @return
    83. */
    84. @Bean
    85. public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
    86. JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    87. //绑定数据源
    88. tokenRepository.setDataSource(dataSource);
    89. return tokenRepository;
    90. }
    91. public static void main(String[] args) {
    92. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    93. String password = "admin";
    94. // 每次都会生成一个随机的salt,同一个明文加密多次编码得到的密文其实都不一样
    95. System.out.println(encoder.encode(password));
    96. System.out.println(encoder.encode(password));
    97. System.out.println(encoder.encode(password));
    98. }
    99. }

           在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置

          文件中定义的标签,而。

          通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过

         标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?

    2、HttpSecurity 类的处理过程

    2.1、HttpSecurity 类图:

             

             由该类图可以清晰得发现,HttpSecurity 继承了父类 AbstractConfiguredSecurityBuilder

             并实现了接口 SecurityBuilder 和 HttpSecurityBuilder

             HttpSecurity 类的定义如下:

                    

    2.2、SecurityBuilder 接口

             SecurityBuilder 定义如下:

    1. public interface SecurityBuilder {
    2. //构建 SecurityBuilder 指定泛型类型的对象
    3. O build() throws Exception;
    4. }

             由接口 SecurityBuilder 的定义可以发现,SecurityBuilder 接口只提供了一个 build() 方法,用

             来构建 SecurityBuilder 泛型指定类型的bean对象。

             结合 HttpSecurity 类中实现 SecurityBuilder 接口时的泛型是什么,就知道在 HttpSecurity 类

             中 SecurityBuilder 是用来创建什么对象,HttpSecurity 定义如下:

                       

              由 HttpSecurity 的定义可以发现,在 HttpSecurity 类中 SecurityBuilder 指定的泛型是

              DefaultSecurityFilterChain,DefaultSecurityFilterChain 是拦截器链SecurityFilterChain

              一个默认实现,所以 DefaultSecurityFilterChain 是一个拦截器链,所以在 HttpSecurity

              中,SecurityBuilder 是用来创建拦截器链的。

    2.2.1、SecurityBuilder.build() 方法的实现

                下面看下 SecurityBuilder.build() 的实现过程,及连接器链的创建过程

                 SecurityBuilder 的默认实现是类 AbstractSecurityBuilder

                 SecurityBuilder.build() 方法的实现如下:

    1. public abstract class AbstractSecurityBuilder implements SecurityBuilder {
    2. //CAS类型
    3. private AtomicBoolean building = new AtomicBoolean();
    4. //返回创建的O的对象
    5. private O object;
    6. public AbstractSecurityBuilder() {
    7. }
    8. //创建泛型O的对象
    9. public final O build() throws Exception {
    10. //基于CAS,保证在整个环境中O只被创建一次
    11. if (this.building.compareAndSet(false, true)) {
    12. //真正创建泛型O的对象
    13. this.object = this.doBuild();
    14. return this.object;
    15. } else {
    16. throw new AlreadyBuiltException("This object has already been built");
    17. }
    18. }
    19. //返回O的对象
    20. public final O getObject() {
    21. if (!this.building.get()) {
    22. throw new IllegalStateException("This object has not been built");
    23. } else {
    24. return this.object;
    25. }
    26. }
    27. //抽象方法,由子类实现
    28. protected abstract O doBuild() throws Exception;
    29. }

                 由 AbstractSecurityBuilder 的定义可以发现,真正创建泛型O(在 HttpSecurity 中O是

                 拦截器链 DefaultSecurityFilterChain )的对象是在doBuild 发给发中完成,而doBuild是

                 一个抽象方法,由子类 AbstractConfiguredSecurityBuilder 实现,

                 doBuild 方法实现如下:

    1. protected final O doBuild() throws Exception {
    2. synchronized(this.configurers) {
    3. this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
    4. this.beforeInit();
    5. //执行当前类的init方法进行初始化操作
    6. this.init();
    7. this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
    8. this.beforeConfigure();
    9. this.configure();
    10. this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
    11. //获取构建的对象,上面的方法可以先忽略
    12. O result = this.performBuild();
    13. this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
    14. return result;
    15. }
    16. }

                 performBuild() 是一个抽象方法,由AuthenticationManagerBuilder、HttpSecurity、

                 WebSecurity 三个子类实现,在这里我们应该看 HttpSecurity 中的实现

                 HttpSecurity.performBuild() 方法的实现如下:        

    1. @Override
    2. protected DefaultSecurityFilterChain performBuild() {
    3. //filters:保存所有的过滤器
    4. // 对所有的过滤器做排序
    5. this.filters.sort(OrderComparator.INSTANCE);
    6. List sortedFilters = new ArrayList<>(this.filters.size());
    7. for (Filter filter : this.filters) {
    8. sortedFilters.add(((OrderedFilter) filter).filter);
    9. }
    10. // 然后生成 DefaultSecurityFilterChain
    11. return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
    12. }

                 DefaultSecurityFilterChain 构造方法如下:

    1. public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List filters) {
    2. if (!filters.isEmpty()) {
    3. logger.info(LogMessage.format("Will not secure %s", requestMatcher));
    4. } else {
    5. logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
    6. }
    7. //绑定请求匹配规则
    8. this.requestMatcher = requestMatcher;
    9. //绑定过滤器集合
    10. this.filters = new ArrayList(filters);
    11. }

                 在HttpSecurity构造方法中绑定了对应的请求匹配器和过滤器集合。

                         

                 对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心

                默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析

    2.3、AbstractConfiguredSecurityBuilder

            AbstractConfiguredSecurityBuilder 是一个抽象类,也可以看成是 SecurityBuilder 接口的

            实现,AbstractConfiguredSecurityBuilder 类图如下所示:

                   

                   

    类型功能
    SecurityBuilder声明了build方法
    AbstractSecurityBuilder提供了获取对象的方法以及控制一个对象只能build一次
    AbstractConfiguredSecurityBuilder除了提供对对象细粒度的控制外还扩展了对configurer的操作

           AbstractConfiguredSecurityBuilder 对应的三个实现类,如下所示:

                       

    2.3.1、BuildState

                AbstractConfiguredSecurityBuilder 中定义了一个枚举类BuildState,将整个构建过程

                分为 5 种状态,也可 以理解为构建过程生命周期的五个阶段,通过这些阶段来管理需

               要构建的对象的不同阶段 如下:   

    1. private enum BuildState {
    2. /**
    3. * 还没开始构建
    4. */
    5. UNBUILT(0),
    6. /**
    7. * 构建中
    8. */
    9. INITIALIZING(1),
    10. /**
    11. * 配置中
    12. */
    13. CONFIGURING(2),
    14. /**
    15. * 构建中
    16. */
    17. BUILDING(3),
    18. /**
    19. * 构建完成
    20. */
    21. BUILT(4);
    22. private final int order;
    23. BuildState(int order) {
    24. this.order = order;
    25. }
    26. public boolean isInitializing() {
    27. return INITIALIZING.order == this.order;
    28. }
    29. /**
    30. * Determines if the state is CONFIGURING or later
    31. * @return
    32. */
    33. public boolean isConfigured() {
    34. return this.order >= CONFIGURING.order;
    35. }
    36. }

              

    2.3.2、AbstractConfiguredSecurityBuilder 常见方法

    2.3.2.1、add() 方法

                  add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到

                 configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,

                 key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当

                 需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,

                  这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。

                  add 方法代码如下:        

    1. private extends SecurityConfigurer> void add(C configurer) {
    2. Assert.notNull(configurer, "configurer cannot be null");
    3. //configurer必须是 SecurityConfigurer 的子类
    4. Classextends SecurityConfigurer> clazz = configurer.getClass();
    5. synchronized(this.configurers) {
    6. if (this.buildState.isConfigured()) {
    7. throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
    8. } else {
    9. List> configs = null;
    10. if (this.allowConfigurersOfSameType) {
    11. configs = (List)this.configurers.get(clazz);
    12. }
    13. List> configs = configs != null ? configs : new ArrayList(1);
    14. ((List)configs).add(configurer);
    15. this.configurers.put(clazz, configs);
    16. if (this.buildState.isInitializing()) {
    17. this.configurersAddedInInitializing.add(configurer);
    18. }
    19. }
    20. }
    21. }
    22. //获取指定的配置类
    23. @SuppressWarnings("unchecked")
    24. public extends SecurityConfigurer> List getConfigurers(Class clazz) {
    25. List configs = (List) this.configurers.get(clazz);
    26. if (configs == null) {
    27. return new ArrayList<>();
    28. }
    29. return new ArrayList<>(configs);
    30. }

    2.3.2.2、doBuild()方法      

    1. @Override
    2. protected final O doBuild() throws Exception {
    3. synchronized (this.configurers) {
    4. this.buildState = BuildState.INITIALIZING;
    5. beforeInit(); //是一个预留方法,没有任何实现
    6. init(); // 就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化,完成默认过滤器的初始化
    7. this.buildState = BuildState.CONFIGURING;
    8. beforeConfigure(); // 是一个预留方法,没有任何实现
    9. configure(); // 就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。
    10. this.buildState = BuildState.BUILDING;
    11. O result = performBuild();
    12. // 是真正的过滤器链构建方法,但是在 AbstractConfiguredSecurityBuilder中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中
    13. this.buildState = BuildState.BUILT;
    14. return result;
    15. }
    16. }

     init方法:完成所有相关过滤器的初始化

    1. private void init() throws Exception {
    2. Collection> configurers = getConfigurers();
    3. for (SecurityConfigurer configurer : configurers) {
    4. configurer.init((B) this); // 初始化对应的过滤器
    5. }
    6. for (SecurityConfigurer configurer : this.configurersAddedInInitializing) {
    7. configurer.init((B) this);
    8. }
    9. }

    configure方法:完成HttpSecurity和对应的过滤器的绑定。

    1. private void configure() throws Exception {
    2. Collection> configurers = getConfigurers();
    3. for (SecurityConfigurer configurer : configurers) {
    4. configurer.configure((B) this);
    5. }
    6. }

    2.4、HttpSecurity

             HttpSecurity 做的事情,就是对各种各样的 xxxConfigurer 进行配置;

             HttpSecurity 部分方法列表如下:

                    

             HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们

              就不一一介绍 了。每个配置方法的结尾都会调用一次 getOrApply 方法,getOrApply

             方法是做什么的?

             getOrApply 方法如下:

    1. private extends SecurityConfigurerAdapter> C getOrApply(C configurer)
    2. throws Exception {
    3. C existingConfig = (C) getConfigurer(configurer.getClass());
    4. if (existingConfig != null) {
    5. return existingConfig;
    6. }
    7. return apply(configurer);
    8. }

              getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就

              是去查看当前 这个 xxxConfigurer 是否已经配置过了。   

              如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply

               方法最终会调 用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置

              configurer 收集起来 HttpSecurity 中还有一个 addFilter 方法.

              addFilter 方法如下所示:

    1. @Override
    2. public HttpSecurity addFilter(Filter filter) {
    3. Integer order = this.filterOrders.getOrder(filter.getClass());
    4. if (order == null) {
    5. throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
    6. + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
    7. }
    8. this.filters.add(new OrderedFilter(filter, order));
    9. return this;
    10. }

              这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这

             个方法, (xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。

    3、总结

          这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简

          单的重 复的 xxxConfigurer 配置了

  • 相关阅读:
    eclipse tomcat setting
    WebRTC安全架构
    RocketMQ 消费者分类与分组
    RabbitMQ学习01
    将任意一组非线性增长的数均匀映射至0到1上
    AWS架构师认证有什么用?考试难吗?
    day08PKI以及综合实验
    【开源】基于微信小程序的音乐平台
    Debian 11 服务器配置日记
    java计算机毕业设计框架的企业机械设备智能管理系统的设计与实现源码+数据库+系统+lw文档+mybatis+运行部署
  • 原文地址:https://blog.csdn.net/liang8999/article/details/142132169