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 中一些方法的重写来改变这个过滤器链中的内容,从而达到定制的目的就可以了。

  • 相关阅读:
    (III)AOP:第六节:通知
    (III)AOP:第八节:多切面运行顺序
    (IV)JdbcTemplate
    (III)AOP:第三节:AOP配置与使用
    (III)AOP:第十节:AOP 总结
    (III)AOP:第二节:AOP概述
    (III)AOP:第四节:切入点表达式
    (III)AOP:第九节:XML配置切面
    (III)AOP:第五节:当前连接点
    7月3号
  • 原文地址:https://www.cnblogs.com/hzhuxin/p/10758834.html
Copyright © 2011-2022 走看看