Spring Security详解(二)认证之核心配置详解

2.核心配置详解

2.1 测试用例

这是Spring Security官方提供的入门示例:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.and()
			.logout()
				.permitAll();
	}

	@Bean
	@Override
	public UserDetailsService userDetailsService() {
		UserDetails user =
			 User.withDefaultPasswordEncoder()
				.username("user")
				.password("password")
				.roles("USER")
				.build();

		return new InMemoryUserDetailsManager(user);
	}
}

从上面的示例可以看到,WebSecurityConfig上有两个注解:

  1. @Configuration:,Spring Configuration,用来注册bean。详情可以查看Configuration详解
  2. @EnableWebSecurity:用来开启Spring Security支持。如果需要自定义配置,可扩展WebSecurityConfigurerAdapter并覆盖其一些方法来设置Web安全配置的某些细节。
  3. @EnableGlobalMethodSecurity(prePostEnabled = true),用来开启@PreFilter@PreAuthorize@PostAuthorize@PostFilter注解的支持

解释一下这样配置的作用:

  • configure(HttpSecurity)方法定义应保护哪些URL路径,不应该保护哪些URL路径。具体来说, //home路径配置为不需要任何身份验证。所有其他路径必须经过验证。

  • 用户成功登录后,他们将被重定向到之前要求身份验证的页面。有一个自定义/login页面(由指定loginPage()),每个人都可以查看它。

  • userDetailsService()方法与单个用户一起建立内存用户存储。该用户的用户名为user,密码为password,角色为USER

2.2 @EnableWebSecurity

@Import({ WebSecurityConfiguration.class,
         SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

    boolean debug() default false;
}

@Import是用于Spring Boot提供的引入外部配置的注解,具体如何加载可查看bean加载

WebSecurityConfiguration

用来配置web Security。在这个类中有一个非常重要的Bean被注册了。

private WebSecurity webSecurity;

@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();
}

通过WebSecurity#build创建了filter springSecurityFilterChain,build方法在WebSecurity详细说明。

WebSecurity的初始化是在:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
    ObjectPostProcessor<Object> objectPostProcessor,
    @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
    throws Exception {
    webSecurity = objectPostProcessor
        .postProcess(new WebSecurity(objectPostProcessor));
    if (debugEnabled != null) {
        webSecurity.debug(debugEnabled);
    }

    Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
	//...
    
    for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
        webSecurity.apply(webSecurityConfigurer);
    }
    this.webSecurityConfigurers = webSecurityConfigurers;
}

创建WebSecurity的实例,并将WebSecurityConfigurer设置到实例中。那么WebSecurityConfigurer来自于哪里呢? 注意看入参 @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers

public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
    ConfigurableListableBeanFactory beanFactory) {
    return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}

//AutowiredWebSecurityConfigurersIgnoreParents#getWebSecurityConfigurers
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
    List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
    Map<String, WebSecurityConfigurer> beansOfType = beanFactory
        .getBeansOfType(WebSecurityConfigurer.class);
    for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
        webSecurityConfigurers.add(entry.getValue());
    }
    return webSecurityConfigurers;
}

获取注册在Spring容器中的所有WebSecurityConfigurer bean。比如测试用例WebSecurityConfig。如果没有自定义的实现类,即没有配置的webSecurityConfigurers,则会new 一个WebSecurityConfigurerAdapter作为默认的webSecurityConfigurers

SpringWebMvcImportSelector

如果当前的环境包含Spring MVC时,需要加载WebMvcSecurityConfiguration,该配置文件用于用于为Spring MVC和Spring Security添加CSRF。

class SpringWebMvcImportSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        boolean webmvcPresent = ClassUtils.isPresent(
            "org.springframework.web.servlet.DispatcherServlet",
            getClass().getClassLoader());
        return webmvcPresent
            ? new String[] {
            "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" }
        : new String[] {};
    }

EnableGlobalAuthentication

源码:

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

可以看出,这个注解引入了AuthenticationConfiguration。主要用来初始化AuthenticationManager

@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

    private AuthenticationManager authenticationManager;

    @Bean
    //创建默认的DefaultPasswordEncoderAuthenticationManagerBuilder 并注册EventPublisher
    public AuthenticationManagerBuilder authenticationManagerBuilder(
        ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
        LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
        AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

        DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
        if (authenticationEventPublisher != null) {
            result.authenticationEventPublisher(authenticationEventPublisher);
        }
        return result;
    }

    public AuthenticationManager getAuthenticationManager() throws Exception {
        if (this.authenticationManagerInitialized) {
            return this.authenticationManager;
        }
        AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
            this.objectPostProcessor, this.applicationContext);
        if (this.buildingAuthenticationManager.getAndSet(true)) {
            return new AuthenticationManagerDelegator(authBuilder);
        }

        for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
            authBuilder.apply(config);
        }

        authenticationManager = authBuilder.build();

        if (authenticationManager == null) {
            //从beanFactory中getBean(AuthenticationManager.class)
            authenticationManager = getAuthenticationManagerBean();
        }

        this.authenticationManagerInitialized = true;
        return authenticationManager;
    }

}

WebSecurity一样,首先创建AuthenticationManagerBuilder实例,然后通过AuthenticationManagerBuilder#build``创建AuthenticationManager。而``globalAuthConfigurers``同样和WebSecurity一样,可以通过像测试用例一样实现GlobalAuthenticationConfigurerAdapter` 来配置全局的AuthenticationManagerBuilder

2.3 @EnableGlobalMethodSecurity

@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {

	
	boolean prePostEnabled() default false;

	
	boolean securedEnabled() default false;

	
	boolean jsr250Enabled() default false;

	
	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;
}
  • prePostEnabled=true,用来开启@PreFilter@PreAuthorize@PostAuthorize@PostFilter注解的支持.
  • securedEnabled=true,开启@Secured注解的支持
  • jsr250Enabled=true,开启JSR-250 注解的支持,如@RolesAllowed,@PermitAll,@DenyAll

@EnableGlobalAuthentication上面已经说过了,这里主要看下GlobalMethodSecuritySelector

GlobalMethodSecuritySelector

final class GlobalMethodSecuritySelector implements ImportSelector {

    public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
        Map<String, Object> annotationAttributes = importingClassMetadata
            .getAnnotationAttributes(annoType.getName(), false);
        AnnotationAttributes attributes = AnnotationAttributes
            .fromMap(annotationAttributes);
        Assert.notNull(attributes, String.format(
            "@%s is not present on importing class '%s' as expected",
            annoType.getSimpleName(), importingClassMetadata.getClassName()));

        // TODO would be nice if could use BeanClassLoaderAware (does not work)
        Class<?> importingClass = ClassUtils
            .resolveClassName(importingClassMetadata.getClassName(),
                              ClassUtils.getDefaultClassLoader());
        boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class
            .isAssignableFrom(importingClass);
		//@EnableGlobalMethodSecurity的mode属性
        AdviceMode mode = attributes.getEnum("mode");
        boolean isProxy = AdviceMode.PROXY == mode;
        String autoProxyClassName = isProxy ? AutoProxyRegistrar.class
            .getName() : GlobalMethodSecurityAspectJAutoProxyRegistrar.class
            .getName();

        //@EnableGlobalMethodSecurity的jsr250Enabled属性
        boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");

        List<String> classNames = new ArrayList<>(4);
        if(isProxy) {
            classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
        }

        classNames.add(autoProxyClassName);

        if (!skipMethodSecurityConfiguration) {
            classNames.add(GlobalMethodSecurityConfiguration.class.getName());
        }

        if (jsr250Enabled) {
            classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
        }

        return classNames.toArray(new String[0]);
    }
}

根据注解配置的不同加载不同的Configuration。

  • 首先处理 @EnableGlobalMethodSecurity 上的modejsr250Enabled 属性。将 GlobalMethodSecurityConfiguration 添加到扫描的list中。
  • mode=PROXY,加载 MethodSecurityMetadataSourceAdvisorRegistrar ,否则加载GlobalMethodSecurityAspectJAutoProxyRegistrar`,默认为前者。
  • jsr250Enabled =true,加载 Jsr250MetadataSourceConfiguration

GlobalMethodSecurityConfiguration

该类主要配置了鉴权时的一些基础信息。

@Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception {
    this.methodSecurityInterceptor = isAspectJ()
        ? new AspectJMethodSecurityInterceptor()
        : new MethodSecurityInterceptor();
    methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
    methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
    methodSecurityInterceptor
        .setSecurityMetadataSource(methodSecurityMetadataSource());
    RunAsManager runAsManager = runAsManager();
    if (runAsManager != null) {
        methodSecurityInterceptor.setRunAsManager(runAsManager);
    }

    return this.methodSecurityInterceptor;
}

MethodInterceptor

  • 默认为MethodSecurityInterceptor,当在方法上注释了@PreAuthorize,会在这里验证权限,这一部分会在鉴权里详细展开。
  • setAccessDecisionManager,默认为AffirmativeBased,鉴权时统计投票结果的,默认进行投票的是RoleVoterAuthenticatedVoter,如果prePostEnabled=true,会使用 PreInvocationAuthorizationAdviceVoter来进行投票。
  • afterInvocationManager,处理鉴权后的返回结果,默认返回null,如果prePostEnabled=true,则会返回AfterInvocationProviderManager
  • setSecurityMetadataSource,用来保存自定义权限的元数据,鉴权时使用,如prePostEnabled=true时,会解析@PreAuthorize
  • afterSingletonsInstantiated,设置AuthenticationManagerMethodInterceptor,这里的AuthenticationManager来自于AuthenticationConfiguration

MethodSecurityMetadataSourceAdvisorRegistrar

class MethodSecurityMetadataSourceAdvisorRegistrar implements
    ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {

        BeanDefinitionBuilder advisor = BeanDefinitionBuilder
            .rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
        advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        advisor.addConstructorArgValue("methodSecurityInterceptor");
        advisor.addConstructorArgReference("methodSecurityMetadataSource");
        advisor.addConstructorArgValue("methodSecurityMetadataSource");

        MultiValueMap<String, Object> attributes = importingClassMetadata.getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
        Integer order = (Integer) attributes.getFirst("order");
        if(order != null) {
            advisor.addPropertyValue("order", order);
        }

        registry.registerBeanDefinition("metaDataSourceAdvisor",
                                        advisor.getBeanDefinition());
    }
}

注册bean MethodSecurityMetadataSourceAdvisor,将 methodSecurityInterceptormethodSecurityMetadataSource 注入到构造方法。当调用方法时,该类会返回对应的MethodInterceptor,MethodInterceptor用于AOP。

Jsr250MetadataSourceConfiguration

@Configuration
class Jsr250MetadataSourceConfiguration {

    @Bean
    public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
        return new Jsr250MethodSecurityMetadataSource();
    }
}

SecurityMetadataSource用来解析@EnableGlobalMethodSecurity配置所支持的注解。Jsr250MethodSecurityMetadataSource用来解析 @RolesAllowed,@PermitAll,@DenyAll

接下来详细分析一下 build方法。

2.4 SecurityBuilder

HttpSecurityWebSecurityAnthenticationManagerBuilder都实现了SecurityBuilder,首先来看下SecurityBuilder的源码:

public interface SecurityBuilder<O> {

   /**
    * Builds the object and returns it or null.
    */
   O build() throws Exception;
}

只有一个build方法,用来构建一个对象

AbstractSecurityBuilder实现了SecurityBuilder,并使用CAS来调用build。

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
	private AtomicBoolean building = new AtomicBoolean();

	private O object;

	public final O build() throws Exception {
        //CAS 构建对象。
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

	public final O getObject() {
		if (!this.building.get()) {
			throw new IllegalStateException("This object has not been built");
		}
		return this.object;
	}

	//抽象方法build
	protected abstract O doBuild() throws Exception;
}

抽象类AbstractConfiguredSecurityBuilder 继承了AbstractSecurityBuilder

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
    extends AbstractSecurityBuilder<O> {
    //属性
    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();

	//apply
    public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
        add(configurer);
        return configurer;
    }
    private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
        Assert.notNull(configurer, "configurer cannot be null");

        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
            .getClass();
        synchronized (configurers) {
            if (buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer
                                                + " to already built object");
            }
            List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
                .get(clazz) : null;
            if (configs == null) {
                configs = new ArrayList<SecurityConfigurer<O, B>>(1);
            }
            configs.add(configurer);
            this.configurers.put(clazz, configs);
            if (buildState.isInitializing()) {
                this.configurersAddedInInitializing.add(configurer);
            }
        }
    }

	//build
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();//SecurityConfigurer#init方法

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }
}
  • 属性configurers,调用apply方法时会保存SecurityConfigurer
  • doBuild方法
    • beforeInit(),默认什么都不做
    • init(),调用SecurityConfigurer 的init方法
    • beforeConfigure(),默认什么都不做
    • configure(),调用SecurityConfigurer 的configure方法
    • performBuild(),抽象方法 ,提供给子类实现。

接下来具体解释一下每个子类的构建方法

WebSecurity

从上文得知,WebSecurity#build是用来构建filter的,源码如下:

protected Filter performBuild() throws Exception {

    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
        chainSize);
    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
        securityFilterChains.add(securityFilterChainBuilder.build());
    }
    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    if (httpFirewall != null) {
        filterChainProxy.setFirewall(httpFirewall);
    }
    filterChainProxy.afterPropertiesSet();

    Filter result = filterChainProxy;
    postBuildAction.run();
    return result;
}

创建FilterChainProxy,并为其添加Security过滤器链。如果配置了ignoredRequest,如:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
            .antMatchers("/resource/**");
    }
}

会將DefaultSecurityFilterChain添加到链。将其余配置的也添加到链。

WebSecurity还有很多其它方法,这里只列个大概:

image-20210219163008276

HttpSecurity

HttpSecurity#build调用目前没有在源码中看到,不过还是解读一下它的源码:

protected DefaultSecurityFilterChain performBuild() throws Exception {
    Collections.sort(filters, comparator);
    return new DefaultSecurityFilterChain(requestMatcher, filters);
}

返回一个默认的过滤器链,而filters的来源自 #addFilter 方法,比如:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.addFilter(usernamePasswordAuthenticationFilter());
        http.addFilterBefore(authenticationTokenFilterBean(),UsernamePasswordAuthenticationFilter.class);

    }
}

常用配置有:

@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").access("hasRole('ADMIN') and hasRole('USER')")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .failureForwardUrl("/login?error")
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/bye")
                .permitAll()
                .and()
            .httpBasic()
                .disable();
    }
}

当配置HttpSecurity或者WebSecurity,比如http.authorizeRequests()authorizeRequests会将对应的SecurityConfigurer添加到构建器,构建时调用init方法和configure方法。

public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
      throws Exception {
   ApplicationContext context = getContext();
   return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
         .getRegistry();
}

private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
    C configurer) throws Exception {
    C existingConfig = (C) getConfigurer(configurer.getClass());
    if (existingConfig != null) {
        return existingConfig;
    }
    //还是这个方法
    return apply(configurer);
}

AuthenticationManagerBuilder

这里的构建比较简单,就是创建一个实例,然后将authenticationProvidersparentAuthenticationManager 注入。

protected ProviderManager performBuild() throws Exception {
   
    ProviderManager providerManager = new ProviderManager(authenticationProviders,
                                                          parentAuthenticationManager);
    //...
    providerManager = postProcess(providerManager);
    return providerManager;
}

其它的主要方法有:

public class AuthenticationManagerBuilder {
    //配置父AuthenticationManager
	public AuthenticationManagerBuilder parentAuthenticationManager(
			AuthenticationManager authenticationManager) {
		...
	}
    //配置事件发布器
	public AuthenticationManagerBuilder authenticationEventPublisher(
			AuthenticationEventPublisher eventPublisher) {
		...
	}
    //认证结束后是否删除密码凭证
	public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
		...
	}
    //配置内存用户
	public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
			throws Exception {
		return apply(new InMemoryUserDetailsManagerConfigurer<>());
	}
    //配置数据库用户
	public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
			throws Exception {
		return apply(new JdbcUserDetailsManagerConfigurer<>());
	}
    //配置userDetailService
	public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
			T userDetailsService) throws Exception {
		...
	}
    //配置authenticationProvider
	public AuthenticationManagerBuilder authenticationProvider(
			AuthenticationProvider authenticationProvider) {
		this.authenticationProviders.add(authenticationProvider);
		return this;
	}
}

具体根据实际情况进行配置,注意:UserDetailService会默认加载DaoAuthenticationProvider

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(jwtUserDetailsServiceImpl()).passwordEncoder(passwordEncoder());
}

这里还要做一个说明AuthenticationManagerBuilder有两种配置方式,一种是如上,还有一种是文章开头的配置,如果你的应用只有唯一一个WebSecurityConfigurerAdapter,那么他们之间的差距可以被忽略,从方法名可以看出两者的区别:使用@Autowired注入的AuthenticationManagerBuilder是全局的身份认证器,作用域可以跨越多个WebSecurityConfigurerAdapter,以及影响到基于Method的安全控制;而 protected configure()的方式则类似于一个匿名内部类,它的作用域局限于一个WebSecurityConfigurerAdapter内部。

2.5 SecurityConfigurer

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
 
    void init(B builder) throws Exception;

    void configure(B builder) throws Exception;
}
  • init,用来init SecurityBuilder
  • configure:配置SecurityBuilder

SecurityConfigurer有两个比较重要的实现类,一个是WebSecurityConfigurerAdapter还有一个是ExpressionUrlAuthorizationConfigurer

WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter默认配置了一部分Security信息,

public void init(final WebSecurity web) throws Exception {
    final HttpSecurity http = getHttp();//初始化http
    web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
        public void run() {
            FilterSecurityInterceptor securityInterceptor = http
                .getSharedObject(FilterSecurityInterceptor.class);
            //添加FilterSecurityInterceptor
            web.securityInterceptor(securityInterceptor);
        }
    });
}

初始化http时会默认添加filter

protected final HttpSecurity getHttp(){
    //...
    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                            sharedObjects);
    //默认为false
    if (!disableDefaults) {
        // @formatter:off
        http
            .csrf().and()
            .addFilter(new WebAsyncManagerIntegrationFilter())
            .exceptionHandling().and()
            .headers().and()
            .sessionManagement().and()
            .securityContext().and()
            .requestCache().and()
            .anonymous().and()
            .servletApi().and()
            .apply(new DefaultLoginPageConfigurer<>()).and()
            .logout();
        // @formatter:on
        ClassLoader classLoader = this.context.getClassLoader();
        List<AbstractHttpConfigurer> defaultHttpConfigurers =
            SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

        for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
            http.apply(configurer);
        }
    }
}

这里通过配置将对应的SecurityConfigurer添加到SecurityBuilder,然后再执行configure方法时添加filter到httpSecurity,比如SecurityContextPersistenceFilter,当配置了securityContext()时,将SecurityContextConfigurer添加,而SecurityContextConfigurer#configure执行时,会将SecurityContextPersistenceFilter添加到httpSecurity中。

SecurityContextConfigurer#configure

public void configure(H http) throws Exception {

    SecurityContextRepository securityContextRepository = http
        .getSharedObject(SecurityContextRepository.class);
    if(securityContextRepository == null) {
        securityContextRepository = new HttpSessionSecurityContextRepository();
    }
    SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
        securityContextRepository);
    SessionManagementConfigurer<?> sessionManagement = http
        .getConfigurer(SessionManagementConfigurer.class);
    SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
        : sessionManagement.getSessionCreationPolicy();
    if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
        securityContextFilter.setForceEagerSessionCreation(true);
    }
    securityContextFilter = postProcess(securityContextFilter);
    http.addFilter(securityContextFilter);
}

初始化HttpSecurity,这里会将UserDetailService添加到HttpSecurity中。可以通过继承来实现扩展,比如上面的测试用例,常用的方法有3个:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.and()
			.logout()
				.permitAll();
	}
    
     @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
            .antMatchers("/resource/**");
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(jwtUserDetailsServiceImpl()).passwordEncoder(passwordEncoder());


    }
	
}

ExpressionUrlAuthorizationConfigurer

当你配置了http.authorizeRequests()...时,会加载ExpressionUrlAuthorizationConfigurer,它的configure方法在父类AbstractInterceptUrlConfigurer中,init方法默认什么都不做。

public void configure(H http) throws Exception {
    //解析配在HttpSecurity的url路径
    FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
    if (metadataSource == null) {
        return;
    }
    //创建过滤器
    FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
        http, metadataSource, http.getSharedObject(AuthenticationManager.class));
    if (filterSecurityInterceptorOncePerRequest != null) {
        securityInterceptor
            .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
    }
    securityInterceptor = postProcess(securityInterceptor);
    http.addFilter(securityInterceptor);
    http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
  1. 首先会解析配置的.antMatchers("/", "/home").permitAll()等,封装在SecurityMetadataSource中,以备后面鉴权时使用
  2. 创建filter FilterSecurityInterceptor,用来拦截请求并鉴权,这里只验证配在HttpSecurity中的。要和上文的MethodInterceptor区分开。这里配置的AccessDecisionManager默认依然是AffirmativeBased,默认进行投票的是WebExpressionVoter
  • 18
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值