zoukankan      html  css  js  c++  java
  • SpringSecurity的配置分析

    在分析SpringSecurity前,基于多年前使用SpringSecurity和近年来使用Shiro的经验, SpringSecurity这些年在发展和SpringBoot整合之后,也逃不出以下的一些套路:

     1. 提供一个AuthenticationManager,用于登录认证

     2. 提供一个web的过滤器链,用于保证web请求的安全处理,这个过滤器链中会包括判断用户是否登录,处理用户的认证请求,基于URL的路径检查用户是否能够访问特定的请求等过滤器

     3. 提供一个获取用户数据的接口实现

     4. 提供一个方法拦截器,用于基于方法的细粒度的授权控制

    以上的1、2、3项都是必要的配置,4是增强级配置可以没有

    下面我们来看下 SpringSecurity 如何来完成上述1、2、3的配置

    一. 上文提到过 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 这个类,springboot只要类路径下能找到 WebSecurityConfigurer 接口的实现类后,就不会加载相应的默认配置。 通过重写一些方法来覆盖默认的一些配置

    
    

    @Configuration
    @EnableWebSecurity
    public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {

    
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin(); //拦截所有URL的访问,将不具备USER角色的用户将被导航到登录页
    }

    
    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication().withUser("user").password("password").roles("USER"); //基于内存的用户认证
    }
    }

     

     从上面这个例子可以看出,我们自己实现的子类能够做这些事:

    •    基于url的路径检查哪些当前的访问是不是具备需要的权限(角色),如果不具备相应的角色,则将其导航至表单认证页面
    •    基于AuthenticationManagerBuilder 调用其不同方法实现不同的认证机制

    这样基本能解决授权和认证,但实际使用时我们不会用例子中的内存数据去完成用户认证,而是通过访问后台的数据库等方式去完成认证,所以还需要配置合适的认证管理器AuthenticationManager和合适的获取用户数据的接口UserDetailsService的实现,下面是AuthenticationManager接口和UserDetailsService的源码:

    public interface AuthenticationManager {
        
        Authentication authenticate(Authentication authentication)
                throws AuthenticationException;
    }
    public interface UserDetailsService {
       
        UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; // 用于通过用户名获取用户信息
    }

    二 . 我们发现除了上文提到的SecurityAutoConfiguration自动配置类外,Springboot 还在 spring.factorys 提供UserDetailsServiceAutoConfiguration类,这个配置类中配置了

    UserDetailsService接口的bean实现UserDetailsService, 这是一个基于内存用户数据的实现,当然这是一个默认配置,所以我们只要提供了UserDetailsService接口的bean配置,就将覆盖默认的获取用户的方式
    @Configuration
    @ConditionalOnClass(AuthenticationManager.class)
    @ConditionalOnBean(ObjectPostProcessor.class)
    @ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
            UserDetailsService.class })
    public class UserDetailsServiceAutoConfiguration {
    
        private static final String NOOP_PASSWORD_PREFIX = "{noop}";
    
        private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern
                .compile("^\{.+}.*$");
    
        private static final Log logger = LogFactory
                .getLog(UserDetailsServiceAutoConfiguration.class);
    
        @Bean
        @ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
        @Lazy
        public InMemoryUserDetailsManager inMemoryUserDetailsManager(
                SecurityProperties properties,
                ObjectProvider<PasswordEncoder> passwordEncoder) {
            SecurityProperties.User user = properties.getUser();
            List<String> roles = user.getRoles();
            return new InMemoryUserDetailsManager(User.withUsername(user.getName())
                    .password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
                    .roles(StringUtils.toStringArray(roles)).build());
        }
    
        private String getOrDeducePassword(SecurityProperties.User user,
                PasswordEncoder encoder) {
            String password = user.getPassword();
            if (user.isPasswordGenerated()) {
                logger.info(String.format("%n%nUsing generated security password: %s%n",
                        user.getPassword()));
            }
            if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
                return password;
            }
            return NOOP_PASSWORD_PREFIX + password;
        }
    
    }

    三. 通过上述的分析,认证的用户数据来源和基于对url资源的访问授权检查的配置都有了,就欠缺一个过滤器链了,可以得出这个过滤器链也是有默认配置的,在上文中介绍过

    SecurityAutoConfiguration这个配置类会导入三个配置类  SpringBootWebSecurityConfiguration.class ,  WebSecurityEnablerConfiguration.class, SecurityDataConfiguration. 上文分析了SpringBootWebSecurityConfiguration,现在分析下 WebSecurityEnablerConfiguration ,还是看源码:

    @Configuration
    @ConditionalOnBean(WebSecurityConfigurerAdapter.class)
    @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @EnableWebSecurity
    public class WebSecurityEnablerConfiguration {
    
    }

    通过源码发现引入了 @EnableWebSecurity ,再看EnableWebSecurity源码:

    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import({ WebSecurityConfiguration.class,
            SpringWebMvcImportSelector.class,
            OAuth2ImportSelector.class })
    @EnableGlobalAuthentication
    @Configuration
    public @interface EnableWebSecurity {
    
        /**
         * Controls debugging support for Spring Security. Default is false.
         * @return if true, enables debug support with Spring Security
         */
        boolean debug() default false;
    }

    又发现导入了 WebSecurityConfiguration.class ,在这个类中有如下配置:

    /**
         * Creates the Spring Security Filter Chain
         * @return the {@link Filter} that represents the security filter chain
         * @throws Exception
         */
        @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
        public Filter springSecurityFilterChain() throws Exception {
            boolean hasConfigurers = webSecurityConfigurers != null
                    && !webSecurityConfigurers.isEmpty();
            if (!hasConfigurers) {
                WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                        .postProcess(new WebSecurityConfigurerAdapter() {
                        });
                webSecurity.apply(adapter);
            }
            return webSecurity.build();
        }

    这也就是springSecurity 默认帮我们配置的过滤器链的bean ,先通过写一个单元测试,看看这个默认帮我们配置的过滤器链的bean 里面都包含了哪些过滤器:

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes=App.class)
    public class FilterChainProxyTest {
        @Autowired
        FilterChainProxy chain  ;
        
        @Test
        public void test() {
            SecurityFilterChain filterChain = chain.getFilterChains().get(0)  ;
            List<Filter> filters = filterChain.getFilters() ;
            for(Filter filter:filters) {
                System.out.println( filter.getClass().getSimpleName() );
            }
        }
    
    }

    执行后输出:

    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    DefaultLoginPageGeneratingFilter
    DefaultLogoutPageGeneratingFilter
    BasicAuthenticationFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor


    默认竟然在链中配置了15个过滤器,已经考虑得很周全了, 所以这个bean一般情况下我们不需要去自定义配置,我们可以通过对WebSecurityConfigurerAdapter 中一些方法的重写来改变这个过滤器链中的内容,从而达到定制的目的就可以了。

  • 相关阅读:
    每天一道LeetCode--141.Linked List Cycle(链表环问题)
    每天一道LeetCode--119.Pascal's Triangle II(杨辉三角)
    每天一道LeetCode--118. Pascal's Triangle(杨辉三角)
    CF1277D Let's Play the Words?
    CF1281B Azamon Web Services
    CF1197D Yet Another Subarray Problem
    CF1237D Balanced Playlist
    CF1239A Ivan the Fool and the Probability Theory
    CF1223D Sequence Sorting
    CF1228D Complete Tripartite
  • 原文地址:https://www.cnblogs.com/hzhuxin/p/10758834.html
Copyright © 2011-2022 走看看