2019-07-24 Spring Security核心流程

Spring Security

学习 Spring Security 用法,架构、设计模式等。

核心组件

主要介绍 Spring Security 中常见核心 Java 类以及他们之间的依赖关系,以及整个架构的设计原理。

SecurityContextHolder

SecurityContextHolder 用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在 SecurityContextHolder中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。但这一切的前提,是你在 web 场景下使用 Spring Security

获取当前用户的信息

因为身份信息是与线程绑定的,所以可以在程序的任何地方使用静态方法获取用户信息。一个例子如下:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

getAuthentication() 返回了认证信息,再次 getPrincipal() 返回了身份信息,UserDetails便是 Spring 对身份信息封装的一个接口。AuthenticationUserDetails 的介绍在下面的小节具体讲解,本节重要的内容是介绍 SecurityContextHolder 这个容器。

Authentication

直接上源码:

package org.springframework.security.core;// <1>

public interface Authentication extends Principal, Serializable { // <1>
    Collection getAuthorities(); // <2>

    Object getCredentials();// <2>

    Object getDetails();// <2>

    Object getPrincipal();// <2>

    boolean isAuthenticated();// <2>

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

<1> Authentication 是 spring security 包中的接口,直接继承自 Principal 类,而 Principal 是位于 java.security 包中的。可以见得,Authentication 在 spring security 中是最高级别的身份/认证的抽象。

<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。

还记得1.1节中,authentication.getPrincipal()返回了一个 Object,我们将 Principal 强转成了 Spring Security 中最常用的UserDetails,这在 Spring Security 中非常常见,接口返回 Object,使用 instanceof 判断类型,强转成对应的具体实现类。接口详细解读如下:

  • getAuthorities(),权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
  • getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  • getDetails(),细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的 ip 地址和 sessionId 的值。
  • getPrincipal(),敲黑板!!!最重要的身份信息,大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一。UserDetails 接口将会在下面的小节重点介绍。

Spring Security是如何完成身份认证的?

  1. 用户名和密码被过滤器获取到,封装成 Authentication ,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。
  2. AuthenticationManager 身份管理器负责验证这个 Authentication
  3. 认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(…) 方法,设置到其中。

这是一个抽象的认证流程,而整个过程中,如果不纠结于细节,其实只剩下一个 AuthenticationManager 是我们没有接触过的了,这个身份管理器我们在后面的小节介绍。

AuthenticationManager

初次接触 Spring Security 的朋友相信会被 AuthenticationManagerProviderManagerAuthenticationProvider …这么多相似的 Spring 认证类搞得晕头转向,但只要稍微梳理一下就可以理解清楚它们的联系和设计者的用意。AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说 AuthenticationManager 一般不直接认证,AuthenticationManager 接口的常用实现类 ProviderManager 内部会维护一个 List 列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个 AuthenticationProvider。这样一来四不四就好理解多了?熟悉 shiro 的朋友可以把AuthenticationProvider 理解成 Realm。在默认策略下,只需要通过一个 AuthenticationProvider 的认证,即可被认为是登录成功。

ProviderManager 关键部分源码

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {

    // 维护一个AuthenticationProvider列表
    private List providers = Collections.emptyList();

    public Authentication authenticate(Authentication authentication)
          throws AuthenticationException {
       Class toTest = authentication.getClass();
       AuthenticationException lastException = null;
       Authentication result = null;

       // 依次认证
       for (AuthenticationProvider provider : getProviders()) {
          if (!provider.supports(toTest)) {
             continue;
          }
          try {
             result = provider.authenticate(authentication);

             if (result != null) {
                copyDetails(authentication, result);
                break;
             }
          }
          ...
          catch (AuthenticationException e) {
             lastException = e;
          }
       }
       // 如果有Authentication信息,则直接返回
       if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                 //移除密码
                ((CredentialsContainer) result).eraseCredentials();
            }
             //发布登录成功事件
            eventPublisher.publishAuthenticationSuccess(result);
            return result;
       }
       ...
       //执行到此,说明没有认证成功,包装异常信息
       if (lastException == null) {
          lastException = new ProviderNotFoundException(messages.getMessage(
                "ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
       }
       prepareException(lastException, authentication);
       throw lastException;
    }
}

ProviderManager 中的 List ,会依照次序去认证,认证成功则立即返回,若认证失败则返回 null,下一个 AuthenticationProvider 会继续尝试认证,如果所有认证器都无法认证成功,则 ProviderManager 会抛出一个ProviderNotFoundException 异常。

以上已经把 Spring Security 的整个认证流程都讲述了一遍,简单小结如下:身份信息的存放容器 SecurityContextHolder ,身份信息的抽象 Authentication ,身份认证器 AuthenticationManager 及其认证流程。姑且在这里做一个分隔线。下面来介绍下 AuthenticationProvider 接口的具体实现。

DaoAuthenticationProvider

AuthenticationProvider 最最最常用的一个实现便是 DaoAuthenticationProvider 。顾名思义,Dao 正是数据访问层的缩写,也暗示了这个身份认证器的实现思路。由于本文是一个 Overview ,姑且只给出其 UML 类图:

2019-07-24 Spring Security核心流程_第1张图片

按照我们最直观的思路,怎么去认证一个用户呢?用户前台提交了用户名和密码,而数据库中保存了用户名和密码,认证便是负责比对同一个用户名,提交的密码和保存的密码是否相同便是了。在Spring Security 中。提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken ,而根据用户名加载用户的任务则是交给了 UserDetailsService ,在DaoAuthenticationProvider 中,对应的方法便是 retrieveUser ,虽然有两个参数,但是 retrieveUser 只有第一个参数起主要作,返回一个 UserDetails。还需要完成 UsernamePasswordAuthenticationTokenUserDetails 密码的比对,这便是交给 additionalAuthenticationChecks 方法完成的,如果这个 void 方法没有抛异常,则认为比对成功。比对密码的过程,用到了PasswordEncoderSaltSource ,密码加密和盐的概念相信不用我赘述了,它们为保障安全而设计,都是比较基础的概念。

如果你已经被这些概念搞得晕头转向了,不妨这么理解 DaoAuthenticationProvider :它获取用户提交的用户名和密码,比对其正确性,如果正确,返回一个数据库中的用户信息(假设用户信息被保存在数据库中)。

UserDetails 与 UserDetailsService

上面不断提到了 UserDetails 这个接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展。

public interface UserDetails extends Serializable {

   Collection getAuthorities();

   String getPassword();

   String getUsername();

   boolean isAccountNonExpired();

   boolean isAccountNonLocked();

   boolean isCredentialsNonExpired();

   boolean isEnabled();
}

它和 Authentication 接口很类似,比如它们都拥有 usernameauthorities ,区分他们也是本文的重点内容之一。AuthenticationgetCredentials()UserDetails 中的 getPassword() 需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication 中的 getAuthorities() 实际是由 UserDetailsgetAuthorities() 传递而形成的。还记得 Authentication 接口中的 getUserDetails() 方法吗?其中的 UserDetails 用户详细信息便是经过了 AuthenticationProvider 之后被填充的。

public interface UserDetailsService {
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetailsServiceAuthenticationProvider 两者的职责常常被人们搞混,关于他们的问题在文档的 FAQissues 中屡见不鲜。记住一点即可,敲黑板!!!UserDetailsService 只负责从特定的地方(通常是数据库)加载用户信息,仅此而已,记住这一点,可以避免走很多弯路。UserDetailsService 常见的实现类有 JdbcDaoImplInMemoryUserDetailsManager,前者从数据库加载用户,后者从内存中加载用户,也可以自己实现 UserDetailsService,通常这更加灵活。

架构概览图

为了更加形象的理解上述我介绍的这些核心类,附上一张按照我的理解,所画出 Spring Security 的一张非典型的 UML

2019-07-24 Spring Security核心流程_第2张图片

整个Spring Security 都是围绕以上这张架构图展开的,最顶层为最核心最抽象的 AuthenticationManager 接口, ProviderManagerAuthenticationManager 的一个具体实现,功能如其名字他的作用是管理 Provider 的, AuthenticationProvider 才是真正认证的接口,因此我们在实践中要实现我们自己的认证方式,也就是 AuthenticationProvider 的一个具体实现。当然可以实现多个,如果有多种认证方式,现实中往往也是有多种认证方式。

注意 AuthenticationProvider 接口中的 supports 方法,ProviderManager 里面其实是维护了一个 AuthenticationProviderList 因此到底使用哪个 Provider 来做验证,可以使用 Filter 返回的 Authentication 实现类来限定,部分代码如下:

//Filter 代码
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
            ......
        return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
    }
//Provider 代码
    @Override
    public boolean supports(Class authentication) {
        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
    } 
//在 ProviderManager 中验证时有如下代码
for (AuthenticationProvider provider : getProviders()) {
   if (!provider.supports(toTest)) {
      continue;
   }

如果对Spring Security 的这些概念感到理解不能,不用担心,因为这是 Architecture First 导致的必然结果,先过个眼熟。后续的文章会秉持 Code First 的理念,陆续详细地讲解这些实现类的使用场景,源码分析,以及最基本的:如何配置 Spring Security ,在后面可以时不时往回看一看,找到具体的类在整个架构中所处的位置。另外,一些 Spring Security的过滤器还未囊括在架构概览中,如将表单信息包装成 UsernamePasswordAuthenticationToken 的过滤器,考虑到这些虽然也是架构的一部分,但是真正重写他们的可能性较小,所以打算放到后面讲解。

配置介绍

Security 安全核心配置例子:

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

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("admin").password("admin").roles("USER");
    }
}

当配置了上述的javaconfig之后,我们的应用便具备了如下的功能:

  • 除了“/”,”/home”(首页),”/login”(登录),”/logout”(注销),之外,其他路径都需要认证。
  • 指定“/login”该路径为登录页面,当未认证的用户尝试访问任何受保护的资源时,都会跳转到“/login”。
  • 默认指定“/logout”为注销页面
  • 配置一个内存中的用户认证器,使用admin/admin作为用户名和密码,具有USER角色
  • 防止CSRF攻击
  • Session Fixation protection
  • Security Header(添加一系列和Header相关的控制)
    • HTTP Strict Transport Security for secure requests
    • 集成X-Content-Type-Options
    • 缓存控制
    • 集成X-XSS-Protection.aspx)
    • X-Frame-Options integration to help prevent Clickjacking(iframe被默认禁止使用)
  • 为Servlet API集成了如下的几个方法
    • HttpServletRequest#getRemoteUser())
    • HttpServletRequest.html#getUserPrincipal())
    • HttpServletRequest.html#isUserInRole(java.lang.String))
    • HttpServletRequest.html#login(java.lang.String, java.lang.String))
    • HttpServletRequest.html#logout())

一个 Restful 配置

动态从数据库加载权限,这边因为 Spring Security 的机制是在模块启动的时候进行加载的,如果想要动态 reload 权限,调查来看Spring 并没有提供相关的接口,需要动态 reload Spring 相关的 bean 是一种比较危险暴力的做法,需要多加注意。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        List permitAllEndpointList = Arrays.asList(AUTHENTICATION_URL,"/api/*/webjars/**","/api/*/swagger**","/api/*/swagger-resources/**","/api/*/v2/api-docs");
        //load Authorities from DB
        ResponseData> authorityRules = authorityService.getAuthority();
        
        try {
            for (AuthorityModel rule : authorityRules.getData()) {
                //这里因为 Restful API url 有相同的情况,因此需要URL和方法名组合来区分,注意一种特殊情况,
                //因为是使用Ant语法来匹配URL,如果出现请求方法相同且URL是匹配子集关系时,要把最具体的URL放在
                //前面,比如同是 GET 方法,Api1:/api/user/paged 和 Api2:/api/user/{id},在Ant语法里面
                //Api2是能匹配Api1的,如果用户有Api2的权限而没有Api1的权限,在如下初始化权限时Api2初始化的
                //顺序在Api1的前面,就会导致用户即使没有Api1的权限也能访问,因此要确保数据库加载的时候Api1在Api2
                //前面
                if (HttpMethod.POST.name().equals(rule.getMethod().name())) {
                    http
                            .authorizeRequests()
                            .antMatchers(HttpMethod.POST, rule.getPattern()).hasAuthority(rule.getSystemName());
                } else if (HttpMethod.GET.name().equals(rule.getMethod().name())) {
                    http
                            .authorizeRequests()
                            .antMatchers(HttpMethod.GET, rule.getPattern()).hasAuthority(rule.getSystemName());
                } else if (HttpMethod.PUT.name().equals(rule.getMethod().name())) {
                    http
                            .authorizeRequests()
                            .antMatchers(HttpMethod.PUT, rule.getPattern()).hasAuthority(rule.getSystemName());
                } else if (HttpMethod.DELETE.name().equals(rule.getMethod().name())) {
                    http
                            .authorizeRequests()
                            .antMatchers(HttpMethod.DELETE, rule.getPattern()).hasAuthority(rule.getSystemName());
                }
            }
        } catch (Exception e) {
            log.error("", e);
        }

        http
            .csrf().disable() // We don't need CSRF for JWT based authentication
            .exceptionHandling().authenticationEntryPoint(this.authenticationEntryPoint)
                //自定义没有权限时的处理,一般根据业务封装自定义的返回结果
                .accessDeniedHandler(ajaxAccessDeniedHandler)

            .and()
                //Restful API 完全无状态,使用JWT token
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                //定义哪些API不需要认证,比如获取 Token 的接口
                .authorizeRequests()
                .antMatchers(permitAllEndpointList.toArray(new String[permitAllEndpointList.size()])).permitAll()
            .and()
                // Protected API End-points
                .authorizeRequests()
                .antMatchers(API_ROOT_URL, API_ATTACHMENT_URL).authenticated()
            .and()
                //处理跨域过滤器
                .addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class)
                //处理登录获取token的过滤器
                .addFilterBefore(buildAjaxLoginProcessingFilter(AUTHENTICATION_URL), UsernamePasswordAuthenticationFilter.class)
                //验证用户token,以及用户是否有接口访问权限的过滤器
                .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(permitAllEndpointList, Arrays.asList(API_ROOT_URL,API_ATTACHMENT_URL)), UsernamePasswordAuthenticationFilter.class);
    }

具体解释见代码上的注释

@EnableWebSecurity

我们自己定义的配置类 WebSecurityConfig 加上了 @EnableWebSecurity 注解,同时继承了 WebSecurityConfigurerAdapter。你可能会在想谁的作用大一点,毫无疑问 @EnableWebSecurity起到决定性的配置作用,它其实是个组合注解。

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,// <2>
        SpringWebMvcImportSelector.class })// <1>
@EnableGlobalAuthentication // <3>
@Configuration
public @interface EnableWebSecurity {
    boolean debug() default false;
}
  • <1> @Importspringboot 提供的用于引入外部的配置的注解,可以理解为:@EnableWebSecurity 注解激活了 @Import 注解中包含的配置类。
  • <2> WebSecurityConfiguration 顾名思义,是用来配置 web 安全的,下面的小节会详细介绍。
  • <3> @EnableGlobalAuthentication 注解的源码如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

注意点同样在 @Import 之中,它实际上激活了 AuthenticationConfiguration 这样的一个配置类,用来配置认证相关的核心类。

也就是说:@EnableWebSecurity完成的工作便是加载了WebSecurityConfigurationAuthenticationConfiguration 这两个核心配置类,也就此将 spring security 的职责划分为了配置安全信息,配置认证信息两部分。

WebSecurityConfiguration

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

@Configuration
public class WebSecurityConfiguration {
    //DEFAULT_FILTER_NAME = "springSecurityFilterChain"
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        ...
    }
 }

在未使用springboot之前,大多数人都应该对springSecurityFilterChain这个名词不会陌生,他是spring security的核心过滤器,是整个认证的入口。在曾经的XML配置中,想要启用spring security,需要在web.xml中进行如下配置:


   
       springSecurityFilterChain
       org.springframework.web.filter.DelegatingFilterProxy
   

   
       springSecurityFilterChain
       /*
   

而在springboot集成之后,这样的XMLjava配置取代。WebSecurityConfiguration中完成了声明springSecurityFilterChain的作用,并且最终交给DelegatingFilterProxy这个代理类,负责拦截请求(注意DelegatingFilterProxy这个类不是spring security包中的,而是存在于web包中,spring使用了代理模式来实现安全过滤的解耦)。

AuthenticationConfiguration

@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
    @Bean
    public AuthenticationManagerBuilder authenticationManagerBuilder(
            ObjectPostProcessor objectPostProcessor) {
        return new AuthenticationManagerBuilder(objectPostProcessor);
    }
    public AuthenticationManager getAuthenticationManager() throws Exception {
        ...
    }
}

AuthenticationConfiguration 的主要任务,便是负责生成全局的身份认证管理者 AuthenticationManager。还记得在《初识 Spring Security 篇一》中,介绍了 Spring Security 的认证体系,AuthenticationManager 便是最核心的身份认证管理器。

WebSecurityConfigurerAdapter

适配器模式在 spring 中被广泛的使用,在配置中使用 Adapter 的好处便是,我们可以选择性的配置想要修改的那一部分配置,而不用覆盖其他不相关的配置。WebSecurityConfigurerAdapter 中我们可以选择自己想要修改的内容,来进行重写,而其提供了三个 configure 重载方法,是我们主要关心的:

    /**WebSecurityConfigurerAdapter**/

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }
    ...
        /**
     * Override this method to configure {@link WebSecurity}. For example, if you wish to
     * ignore certain requests.
     */
    public void configure(WebSecurity web) throws Exception {
    }
    /**
     * Override this method to configure the {@link HttpSecurity}. Typically subclasses
     * should not invoke this method by calling super as it may override their
     * configuration. The default configuration is:
     *
     * 
     * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
     * 
* * @param http the {@link HttpSecurity} to modify * @throws Exception if an error occurs */ // @formatter:off protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); }

由参数就可以知道,分别是对 AuthenticationManagerBuilderWebSecurityHttpSecurity进行个性化的配置。

HttpSecurity常用配置

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

上述是一个使用 Java Configuration 配置 HttpSecurity 的典型配置,其中 http 作为根开始配置,每一个 and() 对应了一个模块的配置(等同于xml配置中的结束标签),并且 and() 返回了 HttpSecurity 本身,于是可以连续进行配置。他们配置的含义也非常容易通过变量本身来推测

  • authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
  • formLogin()对应表单认证相关的配置
  • logout()对应了注销相关的配置
  • httpBasic()可以配置basic登录

他们分别代表了 http 请求相关的安全配置,这些配置项无一例外的返回了 Configurer 类,而所有的 http 相关配置可以通过查看HttpSecurity的主要方法得知:

    public LogoutConfigurer logout() throws Exception {
        return getOrApply(new LogoutConfigurer());
    }
    ......
        public CsrfConfigurer csrf() throws Exception {
        ApplicationContext context = getContext();
        return getOrApply(new CsrfConfigurer(context));
    }

需要对 http 协议有一定的了解才能完全掌握所有的配置,不过,springbootspring security的自动配置已经足够使用了。其中每一项 Configurer(e.g.FormLoginConfigurer,CsrfConfigurer)都是 HttpConfigurer 的细化配置项。

WebSecurityBuilder

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

以笔者的经验,这个配置中并不会出现太多的配置信息。

AuthenticationManagerBuilder

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser("admin").password("admin").roles("USER");
    }
}

想要在 WebSecurityConfigurerAdapter 中进行认证相关的配置,可以使用 configure(AuthenticationManagerBuilder auth) 暴露一个 AuthenticationManager 的建造器:AuthenticationManagerBuilder 。如上所示,我们便完成了内存中用户的配置。

细心的朋友会发现,在前面的文章中我们配置内存中的用户时,似乎不是这么配置的,而是:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("admin").password("admin").roles("USER");
    }
}

如果你的应用只有唯一一个 WebSecurityConfigurerAdapter ,那么他们之间的差距可以被忽略,从方法名可以看出两者的区别:使用@Autowired注入的 AuthenticationManagerBuilder 是全局的身份认证器,作用域可以跨越多个WebSecurityConfigurerAdapter,以及影响到基于Method的安全控制;而 protected configure()的方式则类似于一个匿名内部类,它的作用域局限于一个WebSecurityConfigurerAdapter内部。关于这一点的区别,可以参考我曾经提出的issuespring-security#issues4571。官方文档中,也给出了配置多个WebSecurityConfigurerAdapter的场景以及demo,将在该系列的后续文章中解读

原文链接:http://blog.didispace.com/xjf-spring-security-4/

你可能感兴趣的:(2019-07-24 Spring Security核心流程)