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

原文地址:https://www.cnblogs.com/hzhuxin/p/10758834.html