Security实现源码解析

何为security

首先,引用官方文档的第一句话:

Spring Security is a framework that provides authentication, authorization, and protection against common attacks. With first class support for both imperative and reactive applications, it is the de-facto standard for securing Spring-based applications.

这句话介绍了security是干什么的,它是一个框架,可以给你的系统提供认证,授权和一定的防止攻击的保护的功能,并且可以通过声明式的方式配置。

认证旨在对访问资源的用户的身份和权限进行确认,判断一个用户是否能够访问资源,是否有足够权限访问资源。

授权旨在提供给用户能够访资源的凭证。

核心部分

security核心通过添加一系列过滤器,使得在访问资源(端点endpoint)之前能够对访问者进行过滤,阻止没有权限的访问者。这是整个security框架的基石,后面讲到的授权也是在这个基石之上实现的。

security过滤器有很多,每个过滤器实现了一个单一功能,就如同整个函数流程中调用的一个子函数,为了防止用户定义的过滤器对security整个“函数流程”造成影响,security实现了自己的一套过滤器链,挂载在了Servlet的过滤器链上。过滤器链保存在FilterChainProxy中,由matches方法来选择对应的过滤器链,FilterChainProxy又和DelegatingFilterProxy绑定,来到DelegatingFilterProxy的请求直接由FilterChainProxy处理,DelegatingFilterProxy就是一个Servlet的过滤器,它就起到了连接双方的作用。

security的过滤器链实现的就是一个功能--认证,可以把security整个过滤流程看成一个函数,这个函数调用了很多子函数(过滤器),有尝试获取basic身份的(BasicAuthenticationFilter),有尝试获取bearer等身份的(OAuth2ClientAuthenticationProcessingFilter),有在没有获取到任何身份时赋予用户匿名身份的(AnonymousAuthenticationFilter),在"函数"运行到最后一步之前,这次请求的用户必然有了一个身份,然后进行整个"函数"的最后一步--身份认证(FilterSecurityInterceptor#beforeInvocation),确认用户的身份能够请求这个资源。

对不同的资源(endpoint)会有不同的身份需求,这时security提供了投票器(AccessDecisionVoter),根据不同的投票规则(AccessDecisionManager)来判决,这些“投票器”的产生就是通过声明式的方式产生的,security有一个自己的语法来生成“投票器”。

同时security对扩展非常的友好,程序员可以编写自己的过滤器加入到过滤器链中:security会根据配置类(可能有多个)来生成一个过滤器链,所以过滤器链可以有多个,根据请求路径来作映射,这样在扩展功能的时候就不需要去判断请求是否是需要这个功能了。

过滤器链的生成

讲到,security是通过配置类的方式生成过滤器链的,实际上,security用到了设计模式中的构造者模式。

SecurityBuilder

public interface SecurityBuilder<O> {

	/**
	 * Builds the object and returns it or null.
	 *
	 * @return the Object to be built or null if the implementation allows it.
	 * @throws Exception if an error occurred when building the Object
	 */
	O build() throws Exception;
}

spring security中所有构造器实现的接口,只定义了一个方法,就是build。

HttpSecurityBuilder

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends
      SecurityBuilder<DefaultSecurityFilterChain>

继承了SecurityBuilder,所以表示的含义也是一个构造器的接口,不过它将构造什么东西具体化了,实现这个接口的构造器都是用来构造DefaultSecurityFilterChain这么个东西的--即security的过滤器链。

其中定义了一个接口方法:

H addFilter(Filter filter);

因为这里接口是构造过滤器链的,所以最终就是要往这个过滤器链中添加过滤器,所以这里规定了实现类必须实现添加过滤器的方法。

DefaultSecurityFilterChain

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
   private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
   private final RequestMatcher requestMatcher;
   private final List<Filter> filters;

   public RequestMatcher getRequestMatcher() {
      return requestMatcher;
   }

   public List<Filter> getFilters() {
      return filters;
   }

   public boolean matches(HttpServletRequest request) {
      return requestMatcher.matches(request);
   }
}
public interface SecurityFilterChain {

   boolean matches(HttpServletRequest request);

   List<Filter> getFilters();
}

HttpSecurityBuilder含义中生成的过滤器链,是个final类。这个过滤器链就是spring security整个认证流程所在的过滤器链了,可以看到域中有filters和requestMatcher,也有个matches方法,所以请求和所使用的过滤器链是有对应关系的。

AbstractSecurityBuilder

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

   private O object;

   public final O build() throws Exception {
      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;
   }

   /**
    * Subclasses should implement this to perform the build.
    *
    * @return the object that should be returned by {@link #build()}.
    *
    * @throws Exception if an error occurs
    */
   protected abstract O doBuild() throws Exception;
}

实现SecurityBuilder的抽象类,将构造的结果保留了下来,且只会构造一次,子类通过重写doBuild来详细构造的过程。


SecurityConfigurer

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
   /**
    * Initialize the {@link SecurityBuilder}. Here only shared state should be created
    * and modified, but not properties on the {@link SecurityBuilder} used for building
    * the object. This ensures that the {@link #configure(SecurityBuilder)} method uses
    * the correct shared objects when building. Configurers should be applied here.
    *
    * @param builder
    * @throws Exception
    */
   void init(B builder) throws Exception;

   /**
    * Configure the {@link SecurityBuilder} by setting the necessary properties on the
    * {@link SecurityBuilder}.
    *
    * @param builder
    * @throws Exception
    */
   void configure(B builder) throws Exception;
}

security特有的一种构造方法,通过这个接口可以给构造器添加构造参数,从而影响到构造出来的结果,可以把这个接口理解为一种可拔插的参数,这个参数被赋给了SecurityBuilderSecurityBuilder在构造出目标时,会先调用自己有的所有SecurityConfigurerinitconfigure方法,最后实施最后构建。

通过泛型参数B extends SecurityBuilder<O>SecurityConfigurer可以获取自己配置的构造器的类名。

AbstractConfiguredSecurityBuilder

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
      extends AbstractSecurityBuilder<O>

也是一个构造器,但是他特殊在通过泛型参数中的B extends SecurityBuilder<O>获得了子类的类型,子类在继承AbstractConfiguredSecurityBuilder时,第二个泛型参数必须是这个子类。

这个抽象类中有一个成员变量:

private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

保存了所有的配置自己的配置类(SecurityConfigurer)。

最后在执行构造的时候,回一次调用这些SecurityConfigurerinitconfigure,调用时会把自己(this)作为参数传给SecurityConfigurer

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

      beforeInit();
      init();

      buildState = BuildState.CONFIGURING;

      beforeConfigure();
      configure();

      buildState = BuildState.BUILDING;

      O result = performBuild();

      buildState = BuildState.BUILT;

      return result;
   }
}

private void init() throws Exception {
   Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

   for (SecurityConfigurer<O, B> configurer : configurers) {
      configurer.init((B) this);
   }

   for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
      configurer.init((B) this);
   }
}

private void configure() throws Exception {
   Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

   for (SecurityConfigurer<O, B> configurer : configurers) {
      configurer.configure((B) this);
   }
}

protected void beforeInit() throws Exception {
}
protected void beforeConfigure() throws Exception {
}
protected abstract O performBuild() throws Exception;

最终的构造过程交给了子类去实现--performBuild

HttpSecurity

public final class HttpSecurity extends
      AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
      implements SecurityBuilder<DefaultSecurityFilterChain>,
      HttpSecurityBuilder<HttpSecurity>

从继承关系图和类定义中可以得知:

HttpSecurity是生成DefaultSecurityFilterChain的构造器,能够通过配置的方式添加配置类(SecurityConfigurer)。

因为父类AbstractConfiguredSecurityBuilder的父类AbstractSecurityBuilder已经实现了build方法,所以HttpSecurity只需要实现performBuild方法即可。

前面知道,SecurityConfigurer会在initconfigure方法中对SecurityBuilder进行配置,在这里SecurityConfigurer是静态的,SecurityBuilder是变化的,不同(类型相同,但不是同一个实例)的Builder可以被Configurer配置,Configurer就是流水线上的模具,Builder就是经过的产品。但是最终生成目标的是Builder,所以Configurer还是要调用Builder的方法才能配置这个Builder。HttpSecurity就提供了很多配置自己的方法:

可以看到,这些方法都支持链式调用,返回值要么是HttpSecurity(可以猜到就是返回的this),要么是一个什么Configurer,泛型是HttpSecurity,可以猜到这是一个大的配置中的子配置,可以将配置具体化,这个调用链会调到那个大的配置的子配置的调用链上,最终肯定还会返回到HttpSecurity的调用链。所以可以猜测这些返回是Configurer的方法中,肯定是创建了个Configurer,然后将this传了进去,最后Configurer就可以利用这个this返回到HttpSecurity中,并将具体的配置(自己)配置上去,也是参数化配置的一环。查看源码的确是这样:

public CorsConfigurer<HttpSecurity> cors() throws Exception {
   return getOrApply(new CorsConfigurer<>());
}

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

public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
      throws Exception {
   configurer.addObjectPostProcessor(objectPostProcessor);
   configurer.setBuilder((B) this);	// 这里
   add(configurer);
   return configurer;
}

apply中就将this传给了这个Configurer,也把这个配置放入了自己的配置库中。

这些Configurer都是继承的AbstractHttpConfigurer

AbstractHttpConfigurer

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
      extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B>

不过要清楚,HttpSecurity是构造过滤器链的,它的域中是有这么一个过滤器链的,会由配置类来决定这个过滤器链中有什么过滤器,配置类就可以通过addFilter方法来添加过滤器:

public HttpSecurity addFilter(Filter filter) {
   Class<? extends Filter> filterClass = filter.getClass();
   if (!comparator.isRegistered(filterClass)) {
      throw new IllegalArgumentException(
            "The Filter class "
                  + filterClass.getName()
                  + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
   }
   this.filters.add(filter);
   return this;
}

现在清楚了整个配置过程了:用户可以通过自定义的一个配置类(继承SecurityConfigurer<HttpSecurity>)调用HttpSecurity的方法,用户可以直接添加过滤器(通过addFilter),也可以根据HttpSecurity中的一些提示“方法”来添加配置类(AbstractHttpConfigurer),调用这些提示”方法“和对应配置类的方法,可以通过方法名来清晰的知道可以干些什么,这就类似于声明式。这些配置类最终也是通过addFilter的方式添加了过滤器。

查看AbstractHttpConfigurer和其父类SecurityConfigurerAdapter就知道了,最终是通过disable或者and这两个方法返回到HttpSecurity的:

// AbstractHttpConfigurer的方法
public B disable() {
   getBuilder().removeConfigurer(getClass());
   return getBuilder();
}

// SecurityConfigurerAdapter的方法
public B and() {
   return getBuilder();
}

protected final B getBuilder() {
   if (securityBuilder == null) {
      throw new IllegalStateException("securityBuilder cannot be null");
   }
   return securityBuilder;
}

结合学习spring security时的快速上手的代码:我们先是写了一个配置类,这个类继承WebSecurityConfigurerAdapter,然后重写了configure方法,其中有一个configure的参数就是HttpSecurity

现在查看WebSecurityConfigurerAdapter的继承关系:

果然实现了SecurityConfigurer,但是仔细看:

public abstract class WebSecurityConfigurerAdapter implements
      WebSecurityConfigurer<WebSecurity>
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
      SecurityConfigurer<Filter, T> {

}

配置的是WebSecurity,不是HttpSecurity,那么这个WebSecurity是什么呢?查看源码中的javadoc:

The WebSecurity is created by WebSecurityConfiguration to create the FilterChainProxy known as the Spring Security Filter Chain (springSecurityFilterChain). The springSecurityFilterChain is the Filter that the DelegatingFilterProxy delegates to.
Customizations to the WebSecurity can be made by creating a WebSecurityConfigurer or more likely by overriding WebSecurityConfigurerAdapter.

它是用来构建FilterChainProxy的,FilterChainProxy是啥呢,前面说到了,它就是整个spring security过滤器链机制的入口。

现在就清楚了,WebSecurityConfiguration 中会创建一个WebSecurity,通过spring容器获取到所有WebSecurityConfigurer的实例,然后配置到创建的WebSecurity中,然后调用WebSecuritybuild方法来获取FilterChainProxy 。这样spring security整个框架就融入成功了。

WebSecurityConfiguration又是怎么来的呢?回想spring security的配置步骤,还有一个重要的步骤,在spring可以扫描到的地方配置注解@EnableWebSecurity,这个注解就导入了WebSecurityConfiguration

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

现在回头看,我们快速上手写的配置类的确可以重写一个参数为WebSecurityconfigure方法,但是父类默认实现了一个空的,按理来说我们就可以直接通过configure(WebSecurity)来直接对WebSecurity配置,可以配置些什么看WebSecurity有什么方法就行了。但是官方的快速上手没有叫我们这么做,而是转而去重写configure(HttpSecurity)这个方法,这个方法是WebSecurityConfigurerAdapter提供的,而不是WebSecurityConfigurer接口定义的,那么就不会被WebSecurity直接调用的,WebSecurity只会直接调用configure(WebSecurity)这个方法。

前面已经知道了HttpSecurity是生成过滤器链的,spring security的框架中过滤器链是实现认证的重要部分,而且可以有多条,通过matches确定,那么就可以猜到,这里重写configure(HttpSecurity)肯定就是用来创建一条过滤器链了。

现在就盯着configure(HttpSecurity),看它是怎么被使用的。

WebSecurityConfigurerAdapter

之前知道了它是用来配置WebSecurity,给WebSecurity构造的FilterChainProxy添加过滤器链的(DefaultSecurityFilterChain)。这个过滤器链是由HttpSecurity构造的,所以有了个configure(HttpSecurity)方法。

观察WebSecurityConfigurerAdapter的域,发现有这么一个域:

private HttpSecurity http;

这下知道了WebSecurityConfigurerAdapter只和一个HttpSecurity对应,所以只能用来生成一个过滤器链配置到WebSecurity中。

追踪这个域http,它在方法getHttp()中被使用并返回:

protected final HttpSecurity getHttp() throws Exception {
   if (http != null) {
      return http;
   }

   DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
         .postProcess(new DefaultAuthenticationEventPublisher());
   localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

   AuthenticationManager authenticationManager = authenticationManager();
   authenticationBuilder.parentAuthenticationManager(authenticationManager);
   authenticationBuilder.authenticationEventPublisher(eventPublisher);
   Map<Class<?>, Object> sharedObjects = createSharedObjects();

   http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
         sharedObjects);
   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);
      }
   }
   configure(http);
   return http;
}

前面三行保证了一个实例只会生成一个HttpSecurity

这个方法中new了一个HttpSecurity赋值给http,并且给http进行了很多默认配置,如果不需要这些默认配置,需要重写构造方法,调用有参构造传值true

在进行了默认配置后,就调用方法configure(http)调用了我们重写的配置,我们对过滤器链的配置就在这个起作用了。

然后我们再追踪getHttp这个方法被谁调用了,发现就是在init()方法中被调用的:

public void init(final WebSecurity web) throws Exception {
   final HttpSecurity http = getHttp();
   web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
      FilterSecurityInterceptor securityInterceptor = http
            .getSharedObject(FilterSecurityInterceptor.class);
      web.securityInterceptor(securityInterceptor);
   });
}

前面的知识说了init方法会被WebSecurity调用,所以就在这里将我们配置好的HttpSecurity传给了WebSecurity。而且看方法名addSecurityFilterChainBuilder的确是添加了个过滤器链的构造器,没问题。

WebSecurity

public final class WebSecurity extends
      AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
      SecurityBuilder<Filter>, ApplicationContextAware

实现ApplicationContextAware可以使它获取到ApplicationContext

直接看performBuild方法:

protected Filter performBuild() throws Exception {
   Assert.state(
         !securityFilterChainBuilders.isEmpty(),
         () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
               + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
               + "More advanced users can invoke "
               + WebSecurity.class.getSimpleName()
               + ".addSecurityFilterChainBuilder directly");
   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;
   if (debugEnabled) {
      logger.warn("\n\n"
            + "********************************************************************\n"
            + "**********        Security debugging is enabled.       *************\n"
            + "**********    This may include sensitive information.  *************\n"
            + "**********      Do not use in a production system!     *************\n"
            + "********************************************************************\n\n");
      result = new DebugFilter(filterChainProxy);
   }
   postBuildAction.run();
   return result;
}

SecurityFilterChain就是DefaultSecurityFilterChain实现的接口,它的实现也只有DefaultSecurityFilterChain一个,暂时可以划等号。

可以看到,performBuild方法中就是创建了FilterChainProxy,过滤器链的来源有两种,一个是ignoredRequests,另一个是securityFilterChainBuilders

首先说securityFilterChainBuilders,类型为List<SecurityBuilder<? extends SecurityFilterChain>>,是个List,通过addSecurityFilterChainBuilder填装:

public WebSecurity addSecurityFilterChainBuilder(
      SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
   this.securityFilterChainBuilders.add(securityFilterChainBuilder);
   return this;
}

熟悉吧,就是WebSecurityConfigurerAdapterinit方法调用的方法,所以这个过滤器链来源就是我们写的配置类,而且一开始的Assert规定了至少要有一个配置。

其次是ignoredRequests,它的类型为List<RequestMatcher>,也是个List,泛型是请求的匹配器,配合名字大致可以知道,可以直接配置哪些请求是忽略的,也就是说不走过滤器链的,不被spring security认证的。它是在哪里进行的填充的呢,发现WebSecurity有个内部类中有两个方法可以填充这个List:

// 节约空间就只放出这两个方法了。
public class IgnoredRequestConfigurer
      extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {
   @Override
   public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method,
         String... mvcPatterns) {
      List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
      WebSecurity.this.ignoredRequests.addAll(mvcMatchers);
      return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(),
            mvcMatchers);
   }

   @Override
   protected IgnoredRequestConfigurer chainRequestMatchers(
         List<RequestMatcher> requestMatchers) {
      WebSecurity.this.ignoredRequests.addAll(requestMatchers);
      return this;
   }
}

这是个内部类,没有static关键字修饰,所以很好找到在哪里使用的,一般就是外面那个类里使用的。果然,WebSecurity有一个域private IgnoredRequestConfigurer ignoredRequestRegistry;,通过ignoring方法就可以拿到了:

public IgnoredRequestConfigurer ignoring() {
   return ignoredRequestRegistry;
}

这样看来,我们自定义的配置类中,就可以通过重写configure(WebSecurity)来直接设置那些请求是不需要认证的。

回到performBuild方法中,最后就将ignoredRequest中所有请求匹配器创建的空过滤器链和securityFilterChainBuilder中所有过滤器链构造器构造的过滤器链合在一起赋给了FilterChainProxy


现在,我们知道了过滤器链是怎么生成的:由HttpSecurity构造,

过滤器链是怎么配置到spring securit的整个过滤器体系中的:通过WebSecurityConfigurerAdapterWebSecurity配置上,

统一spring security的过滤器链的代理是怎样生成的:通过WebSecurity生成WebSecurity是由WebSecurityConfiguration 创建的,WebSecurityConfiguration 是由@EnableWebSecurity这个注解导入的。

框架搭建的原理就很清晰了,之后就需要了解各个过滤器干了些什么,它们是怎么组合在一起完成各种功能的。

认证的实现(权限认证部分)

**认证和认证的区别:**这一节可以看到很多次认证,不是每次认证的含义都是相同的,需要根据上下文语义来判断。主要分为两种认证:对用户信息的认证和对用户权限的认证。对用户信息的认证是从请求中根据token或者其他身份标志来获取用户的身份信息(包含用户的权限信息);对用户权限的认证是根据对用户信息的认证获取到的用户权限信息来确认用户是否有资格访问资源,这部分在英文也可能被叫做authorize,直译是授权,但是和后面要讲到的授权又不同了。

前面我们了解到了spring security是怎么通过类似声明式的方式配置出的过滤器链,现在来看看认证是怎么实现的。

对照WebSecurityConfigurerAdapter中给HttpSecurity默认的配置,或者直接运行调试打断点查看过滤器链中有哪些过滤器:

![](images/normal-endpoint-security-filters(include error).png)

图片里少了个CsrfFilter,那是因为我自定义了配置,把csrf取消了。

发现过滤器链中过滤器的顺序和配置顺序不同,这是因为HttpScurityperformBuild时对过滤器进行了排序。

这里面最重要的一个过滤器是最后一个过滤器--FilterSecurityInterceptor,在这个过滤器中,进行了对用户的认证。

Authentication

这是Security中很重要的一个用来描述存储认证信息的接口,它有两个状态:未认证/已认证。未认证时,是用来存储用户传入的认证用的信息,比如账号密码,认证成功后,存储的就是用户权限信息等。

Authentication的“生命周期”如下:

  • AbstractAuthenticationProcessingFilter根据请求中的信息生成Authentication,放入认证用的账号密码
  • AuthenticationManager中使用Authentication进行认证
  • 认证成功后把Authentication放入SecurityContextHolder
  • 后面的过滤器就可以从SecurityContextHolder中拿到认证信息了

SecurityContextHolder

这个类是个的方法全是静态方法,它的目的是用来存储这次请求“谁”(Authentication)被认证了,调用getContext()可以获得这次请求中的SecurityContextSecurityContext调用getAuthentication()就可以获得认证的Authentication,程序员可以写自己的过滤器在认证后通过这种方法获取Authentication来获得认证用户的部分信息,比如用户名,权限,但必须在认证之后才能拿到。注意,SecurityContextHolder存储SecurityContext是用当前线程作为键存储的,如果在其他线程中就无法拿到了。

AbstractSecurityInterceptor

这是spring security实现安全拦截的核心逻辑部分,它是个抽象类,已有方法将安全检查独立出来了,如果需要对某种对象的执行进行保护,则继承它,并实现getSecureObjectClass方法说明保护的是什么对象,实现obtainSecurityMetadataSource方法提供保护对象规则的数据源,从这个数据源会传递给AccessDecisionManager进行访问权限判断。

规定在执行需要安全检查的方法/过程之前,需要先调用beforeInvocation进行权限检查,在执行完方法之后,不管成功还是失败都要执行finallyInvocation,一般放在finally代码块中,最后需要调用afterInvocation来改变方法的返回值。

AbstractSecurityInterceptor类上有javadoc讲解实现逻辑:

Abstract class that implements security interception for secure objects.
The AbstractSecurityInterceptor will ensure the proper startup configuration of the security interceptor. It will also implement the proper handling of secure object invocations, namely:

  1. Obtain the Authentication object from the SecurityContextHolder.
  2. Determine if the request relates to a secured or public invocation by looking up the secure object request against the SecurityMetadataSource.
  3. For an invocation that is secured (there is a list of ConfigAttributes for the secure object invocation):
    a. If either the Authentication.isAuthenticated() returns false, or the alwaysReauthenticate is true, authenticate the request against the configured AuthenticationManager. When authenticated, replace the Authentication object on the SecurityContextHolder with the returned value.
    b. Authorize the request against the configured AccessDecisionManager.
    c. Perform any run-as replacement via the configured RunAsManager.
    d. Pass control back to the concrete subclass, which will actually proceed with executing the object. A InterceptorStatusToken is returned so that after the subclass has finished proceeding with execution of the object, its finally clause can ensure the AbstractSecurityInterceptor is re-called and tidies up correctly using finallyInvocation(InterceptorStatusToken).
    e. The concrete subclass will re-call the AbstractSecurityInterceptor via the afterInvocation(InterceptorStatusToken, Object) method.
    f. If the RunAsManager replaced the Authentication object, return the SecurityContextHolder to the object that existed after the call to AuthenticationManager.
    g. If an AfterInvocationManager is defined, invoke the invocation manager and allow it to replace the object due to be returned to the caller.
  4. For an invocation that is public (there are no ConfigAttributes for the secure object invocation):
    a. As described above, the concrete subclass will be returned an InterceptorStatusToken which is subsequently re-presented to the AbstractSecurityInterceptor after the secure object has been executed. The AbstractSecurityInterceptor will take no further action when its afterInvocation(InterceptorStatusToken, Object) is called.
  5. Control again returns to the concrete subclass, along with the Object that should be returned to the caller. The subclass will then return that result or exception to the original caller.

实现给受保护的对象进行安全拦截的抽象类。
AbstractSecurityInterceptor将确保安全拦截器的正确启动配置。它还将实现对受保护对象调用的正确处理,即:

  1. 从SecurityContextHolder获取Authentication对象。
  2. 通过SecurityMetadataSource判断请求是受保护调用还是公开(不被保护)的调用。
  3. 对于一个受保护的调用(这个受保护的调用有一个ConfigAttributes的List):
    a. 如果Authentication.isAuthenticated()返回false或者alwaysReauthenticate是true,通过配置的AuthenticationManager(重新)认证这个请求。认证后,将SecurityContextHolder中的Authentication替换成认证的结果。
    b. 通过配置的AccessDecisionManager对这个请求进行权限判断。
    c. 通过配置的RunAsManager来替换临时运行环境(参数)。
    d. 将控制权传递回具体的子类,该子类即将真正的执行这个受保护的对象。AbstractSecurityInterceptor的方法会返回一个InterceptorStatusToken(调用beforeInvocation返回的),子类在执行这个受保护对象的finally代码域内需要再次调用AbstractSecurityInterceptor的方法finallyInvocation(InterceptorStatusToken)。
    e. 具体的子类调用AbstractSecurityInterceptor的afterInvocation(InterceptorStatusToken, Object)方法。
    f. 如果RunAsManager替换了Authentication,需要复原,是直接将原来的SecurityContext保存起来了,所以直接恢复SecurityContext。
    g. 如果配置了AfterInvocationManager,调用它并使用它的返回值作为最终返回值。
  4. 对于一个公开的调用(这个调用没有ConfigAttributes):
    a. 类似上面,不过afterInvocation(InterceptorStatusToken, Object)中不会做什么了。
  5. 继续具体子类的运行流程。

重点如下

  1. 在执行保护代码之前,会要进行权限认证,还有可能会通过RunAsManager生成一个替代的Authentication。
  2. 在运行之后会复原Authentication。
  3. 可以配置AfterInvocationManager来改变保护对象的运行返回值。

现在看下源码:

protected InterceptorStatusToken beforeInvocation(Object object) {
   Assert.notNull(object, "Object was null");
   final boolean debug = logger.isDebugEnabled();

   // 确认传过来进行权限判断的对象是合法的
   if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
      throw new IllegalArgumentException(
            "Security invocation attempted for object "
                  + object.getClass().getName()
                  + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                  + getSecureObjectClass());
   }

   // 获取权限要求参数
   Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
         .getAttributes(object);

   if (attributes == null || attributes.isEmpty()) {
      // 如果这个对象没有设置被保护
      if (rejectPublicInvocations) {
         throw new IllegalArgumentException(
               "Secure object invocation "
                     + object
                     + " was denied as public invocations are not allowed via this interceptor. "
                     + "This indicates a configuration error because the "
                     + "rejectPublicInvocations property is set to 'true'");
      }

      if (debug) {
         logger.debug("Public object - authentication not attempted");
      }

      publishEvent(new PublicInvocationEvent(object));

      return null; // no further work post-invocation
   }

   if (debug) {
      logger.debug("Secure object: " + object + "; Attributes: " + attributes);
   }

   // 确认已经获取到了请求者的身份
   if (SecurityContextHolder.getContext().getAuthentication() == null) {
      credentialsNotFound(messages.getMessage(
            "AbstractSecurityInterceptor.authenticationNotFound",
            "An Authentication object was not found in the SecurityContext"),
            object, attributes);
   }

   // 如果没有获取到身份或者需要重新获取,则获取
   Authentication authenticated = authenticateIfRequired();

   // Attempt authorization
   try {
      // 投票机制对用户进行认证
      this.accessDecisionManager.decide(authenticated, object, attributes);
   }
   catch (AccessDeniedException accessDeniedException) {
      publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
            accessDeniedException));

      // 权限不足
      throw accessDeniedException;
   }

   if (debug) {
      logger.debug("Authorization successful");
   }

   if (publishAuthorizationSuccess) {
      publishEvent(new AuthorizedEvent(object, attributes, authenticated));
   }

   // Attempt to run as a different user
   Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
         attributes);

   if (runAs == null) {
      if (debug) {
         logger.debug("RunAsManager did not change Authentication object");
      }

      // no further work post-invocation
      return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
            attributes, object);
   }
   else {
      if (debug) {
         logger.debug("Switching to RunAs Authentication: " + runAs);
      }

      // 身份代理
      SecurityContext origCtx = SecurityContextHolder.getContext();
      SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
      SecurityContextHolder.getContext().setAuthentication(runAs);

      // need to revert to token.Authenticated post-invocation
      return new InterceptorStatusToken(origCtx, true, attributes, object);
   }
}
protected void finallyInvocation(InterceptorStatusToken token) {
   // 可以知道runAs不为null时,就会需要恢复身份
   if (token != null && token.isContextHolderRefreshRequired()) {
      if (logger.isDebugEnabled()) {
         logger.debug("Reverting to original Authentication: "
               + token.getSecurityContext().getAuthentication());
      }

      SecurityContextHolder.setContext(token.getSecurityContext());
   }
}
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
   if (token == null) {
      // public object
      return returnedObject;
   }

   // 防止遗漏调用finallyInvocation
   finallyInvocation(token); // continue to clean in this method for passivity

   if (afterInvocationManager != null) {
      // Attempt after invocation handling
      try {
         // 如果配置了afterInvocationManager,替换返回值
         returnedObject = afterInvocationManager.decide(token.getSecurityContext()
               .getAuthentication(), token.getSecureObject(), token
               .getAttributes(), returnedObject);
      }
      catch (AccessDeniedException accessDeniedException) {
         AuthorizationFailureEvent event = new AuthorizationFailureEvent(
               token.getSecureObject(), token.getAttributes(), token
                     .getSecurityContext().getAuthentication(),
               accessDeniedException);
         publishEvent(event);

         throw accessDeniedException;
      }
   }

   return returnedObject;
}
private Authentication authenticateIfRequired() {
   Authentication authentication = SecurityContextHolder.getContext()
         .getAuthentication();

   if (authentication.isAuthenticated() && !alwaysReauthenticate) {
      if (logger.isDebugEnabled()) {
         logger.debug("Previously Authenticated: " + authentication);
      }

      return authentication;
   }

   authentication = authenticationManager.authenticate(authentication);

   // We don't authenticated.setAuthentication(true), because each provider should do
   // that
   if (logger.isDebugEnabled()) {
      logger.debug("Successfully Authenticated: " + authentication);
   }

   SecurityContextHolder.getContext().setAuthentication(authentication);

   return authentication;
}

用到的是AccessDecisionManager来判断权限。

FilterSecurityInterceptor-请求级别的权限认证

AbstractSecurityInterceptor有两种子类,其中一个就是FilterSecurityInterceptor,保护对象是过滤器链,也就是说它对整个请求进行了保护。

通过调用HttpSecurityauthorizeRequests()方法,可以给Httpsecurity生成的过滤器链添加上这个过滤器FilterSecurityInterceptor

public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
   FilterInvocation fi = new FilterInvocation(request, response, chain);
   invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
   if ((fi.getRequest() != null)
         && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
         && observeOncePerRequest) {
      // filter already applied to this request and user wants us to observe
      // once-per-request handling, so don't re-do security checking
      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
   }
   else {
      // first time this request being called, so perform security checking
      if (fi.getRequest() != null && observeOncePerRequest) {
         fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
      }

      InterceptorStatusToken token = super.beforeInvocation(fi);

      try {
         fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
      }
      finally {
         super.finallyInvocation(token);
      }

      super.afterInvocation(token, null);
   }
}

看注释也能理解,这里进行了security checking。至于为什么有第一个分支,不需要进行安全检查,那是因为后面的处理可能要求转换请求路径再请求一次,这时这个请求的用户已经经过一次安全检查了,就不需要再检查了。什么时候会要更换请求路径呢?最常见的就是调用response.sendError这个方法,将请求转发到/error路径上。

FilterSecurityInterceptorinvoke方法中就使用到了它的父类AbstractSecurityInterceptor提供的认证能力。

过滤器链的调用没有返回值,所以最后调用afterInvocation也只是象征性的调用了下,来符合设计要求。

AccessDecisionManager

AccessDecisionManager中判断权限的方法是decide

void decide(Authentication authentication, Object secureObject,
    Collection<ConfigAttribute> configAttributes) throws AccessDeniedException;

需要三个参数:

  • Authentication:访问资源者的认证信息,包括权限信息
  • secureObject:要访问的受保护的资源
  • configAttributes:受保护资源的访问策略,通过SecurityMetadataSource获取

AccessDecisionManager使用投票机制来确定访问者是否有权限,图中可以看出,AccessDecisionManager由多个AccessDecisionVoter进行投票(调用方法vote(),返回值为int),返回值可能是通过(ACCESS_GRANTED),弃权(ACCESS_ABSTAIN),拒绝(ACCESS_DENIED)。

AccessDecisionManager有多个子类,对应着不同的投票机制:

  • AffirmativeBased的逻辑是:

    (1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;

    (2)如果全部弃权也表示通过;

    (3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。Spring security默认使用的是AffirmativeBased。

  • ConsensusBased的逻辑是:

    (1)如果赞成票多于反对票则表示通过。

    (2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。

    (3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。

    (4)如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

  • UnanimousBased的逻辑是:

    (1)如果受保护对象配置的某一个ConfifigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException。

    (2)如果没有反对票,但是有赞成票,则表示通过。

    (3)如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出AccessDeniedException。

AccessDecisionVoter

AccessDecisionManager会调用自己所有的AccessDecisionVoter来投票

public interface AccessDecisionVoter<S> {
   int ACCESS_GRANTED = 1;
   int ACCESS_ABSTAIN = 0;
   int ACCESS_DENIED = -1;

   boolean supports(ConfigAttribute attribute);

   boolean supports(Class<?> clazz);

   int vote(Authentication authentication, S object,
         Collection<ConfigAttribute> attributes);
}

有根据ConfigAttribute#getAttribute来获取权限要求的Voter,比如RoleVoter和ScopeVoter,它们的权限要求以简单的字符串形式表示,比如ROLE_USER等。也有根据具体的ConfigAttribute的实现类来进行权限判断的Voter,它们会调用对应实现类的特色的方法来判断,而不是使用getAttribute。

RequestMatcher

这是一个对HttpServletRequest进行匹配的工具,就定义了一个matches方法。

AbstractRequestMatcherRegistry

public abstract class AbstractRequestMatcherRegistry<C>

一个用来通过配置生成RequestMatcher的抽象类,泛型中的C是需要用到配置的的RequestMatcher某个类或者这个类的链式编程中的下一环,通过继承这个抽象类来获得几个生成RequestMatcher的方法。

AbstractRequestMatcherRegistry定义并实现了几个方法来根据请求方式和字符串路径生成RequestMatcher

  • public C anyRequest()
  • public C antMatchers(HttpMethod method)
  • public C antMatchers(HttpMethod method, String... antPatterns)
  • public C antMatchers(String... antPatterns)
  • public C regexMatchers(HttpMethod method, String... regexPatterns)
  • public C regexMatchers(String... regexPatterns)
  • public C requestMatchers(RequestMatcher... requestMatchers)

这些方法都是在这个抽象类实现好了的,会生成一个RequestMatcher,并通过某个抽象方法传给子类,比如:

public C antMatchers(String... antPatterns) {
   Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
   return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
}

protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);

子类实现chainRequestMatchers这个方法就可以获得生成的RequestMatcher了。

这些方法都会返回C,返回的C到底是什么也是由chainRequestMatchers方法决定的,我可以返回子类自己,这样只配置一个RequestMatcher就够了,我只需要一个请求匹配器,没有后续要求了。我也可以返回一个AuthorizedUrl,这个类的方法中可以通过链式的方式配置权限要求,这样这个权限要求就可以和RequestMatcher绑定在一起了。

这个类还有一个方法createMvcMatchers,是protected的,可以让子类直接使用,而不是调用时使用:

protected final List<MvcRequestMatcher> createMvcMatchers(HttpMethod method,
      String... mvcPatterns)

RequestMatcher的生成是由这个抽象类的一个类部静态工具类提供的方法实现的:

private static final class RequestMatchers {
   public static List<RequestMatcher> antMatchers(HttpMethod httpMethod,
         String... antPatterns) {
      String method = httpMethod == null ? null : httpMethod.toString();
      List<RequestMatcher> matchers = new ArrayList<>();
      for (String pattern : antPatterns) {
         matchers.add(new AntPathRequestMatcher(pattern, method));
      }
      return matchers;
   }
   
   public static List<RequestMatcher> antMatchers(String... antPatterns) {
      return antMatchers(null, antPatterns);
   }
   
   public static List<RequestMatcher> regexMatchers(HttpMethod httpMethod,
         String... regexPatterns) {
      String method = httpMethod == null ? null : httpMethod.toString();
      List<RequestMatcher> matchers = new ArrayList<>();
      for (String pattern : regexPatterns) {
         matchers.add(new RegexRequestMatcher(pattern, method));
      }
      return matchers;
   }
   
   public static List<RequestMatcher> regexMatchers(String... regexPatterns) {
      return regexMatchers(null, regexPatterns);
   }

   private RequestMatchers() {
   }
}

AbstractConfigAttributeRequestMatcherRegistry

public abstract class AbstractConfigAttributeRequestMatcherRegistry<C> extends
      AbstractRequestMatcherRegistry<C>

AbstractRequestMatcherRegistry的基础上,把RequestMatcher封装了一层,给他添加一个附加的参数列表,并且“含义”变化了,这个抽象类是用来生成一个带有参数的RequestMatcher的,即(UrlMapping),你会发现,这个参数的类型ConfigAttribute正是AccessDecisionVoter#vote中表示权限参数的类型。

static final class UrlMapping {
   private RequestMatcher requestMatcher;
   private Collection<ConfigAttribute> configAttrs;

   UrlMapping(RequestMatcher requestMatcher, Collection<ConfigAttribute> configAttrs) {
      this.requestMatcher = requestMatcher;
      this.configAttrs = configAttrs;
   }

   public RequestMatcher getRequestMatcher() {
      return requestMatcher;
   }

   public Collection<ConfigAttribute> getConfigAttrs() {
      return configAttrs;
   }
}

实现chainRequestMatchers方法,将生成的requestMatcher记录了下来,同时也传给了子类,之后会要求必须继续配置这个requestMatcher对应的参数,最后子类要调用addMapping来完成配置过程,并保存最终结果。

final void addMapping(UrlMapping urlMapping) {
   this.unmappedMatchers = null;
   this.urlMappings.add(urlMapping);
}

protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
   this.unmappedMatchers = requestMatchers;
   return chainRequestMatchersInternal(requestMatchers);
}

protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);

这个抽象类还提供了一个方法来获得映射:

final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
   if (unmappedMatchers != null) {
      throw new IllegalStateException(
            "An incomplete mapping was found for "
                  + unmappedMatchers
                  + ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
   }

   LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
   for (UrlMapping mapping : getUrlMappings()) {
      RequestMatcher matcher = mapping.getRequestMatcher();
      Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
      requestMap.put(matcher, configAttrs);
   }
   return requestMap;
}

可以看到,在配置没有完成时,是不能获得最终结果的。

AbstractInterceptUrlConfigurer

abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>
      extends AbstractHttpConfigurer<C, H>

这就是一个给HttpSecurityBuilder进行配置的配置类。

configure方法就给HttpSecurityBuilder添加了一个FilterSecurityInterceptor,前面也知道了,这个过滤器就是进行权限认证的。

public void configure(H http) throws Exception {
   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);
}

abstract FilterInvocationSecurityMetadataSource createMetadataSource(H http);

private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
      FilterInvocationSecurityMetadataSource metadataSource,
      AuthenticationManager authenticationManager) throws Exception {
   FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
   securityInterceptor.setSecurityMetadataSource(metadataSource);
   securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
   securityInterceptor.setAuthenticationManager(authenticationManager);
   securityInterceptor.afterPropertiesSet();
   return securityInterceptor;
}

创建进行权限判断的FilterInvocationSecurityMetadataSource的方法createMetadataSource是抽象方法,继续交给子类来实现,这个类只用来生成过滤器。

private AccessDecisionManager getAccessDecisionManager(H http) {
   if (accessDecisionManager == null) {
      accessDecisionManager = createDefaultAccessDecisionManager(http);
   }
   return accessDecisionManager;
}
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
   AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
   return postProcess(result);
}
abstract List<AccessDecisionVoter<?>> getDecisionVoters(H http);

过滤器要用到的投票器也交给了子类来生成,不过这里确定了投票规则是AffirmativeBased。

AbstractInterceptUrlRegistry

这个类是AbstractInterceptUrlConfigurer的一个内部类:

abstract class AbstractInterceptUrlRegistry<R extends AbstractInterceptUrlRegistry<R, T>, T>
      extends AbstractConfigAttributeRequestMatcherRegistry<T>

可以看到R的含义就是要子类把自己的类型传递过来,好让这个当父亲的知道要返回值应该填什么类型,这样才能保持调用链不断,不然如果返回的是父类的类型,子类为调用链定义的方法就用不了了。的确有这么个方法获得子类自己:

private R getSelf() {
   return (R) this;
}

这类多定义了两个方法,这样可以由用户来设置accessDecisionManagerfilterSecurityInterceptorOncePerRequest了,而不是由AbstractInterceptUrlConfigurer的子类去设置。

public R accessDecisionManager(AccessDecisionManager accessDecisionManager) {
   AbstractInterceptUrlConfigurer.this.accessDecisionManager = accessDecisionManager;
   return getSelf();
}

public R filterSecurityInterceptorOncePerRequest(
      boolean filterSecurityInterceptorOncePerRequest) {
   AbstractInterceptUrlConfigurer.this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest;
   return getSelf();
}

ExpressionUrlAuthorizationConfigurer

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
      extends
      AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H>

这个类就是我们可以看到在HttpSecurity的方法authorizeRequests的返回值相关的类了,而这个authorizeRequests方法正是快速上手中用来配置不同路径不同权限要求的方法。

这个类有个成员变量:

private final ExpressionInterceptUrlRegistry REGISTRY;

ExpressionInterceptUrlRegistryAbstractInterceptUrlRegistry的子类,可以知道,这个成员就是用来进行生成RequestMatcherConfigAttributes的。通过它就可以获取到用户配置到的匹配器和匹配器对应的权限要求参数。

再看下实现的父抽象类定义的抽象方法:

@Override
List<AccessDecisionVoter<?>> getDecisionVoters(H http) {
   List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
   WebExpressionVoter expressionVoter = new WebExpressionVoter();
   expressionVoter.setExpressionHandler(getExpressionHandler(http));
   decisionVoters.add(expressionVoter);
   return decisionVoters;
}
@Override
ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(
      H http) {
   LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
         .createRequestMap();
   if (requestMap.isEmpty()) {
      throw new IllegalStateException(
            "At least one mapping is required (i.e. authorizeRequests().anyRequest().authenticated())");
   }
   return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
         getExpressionHandler(http));
}
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler(H http) {
   if (expressionHandler == null) {
      DefaultWebSecurityExpressionHandler defaultHandler = new DefaultWebSecurityExpressionHandler();
      AuthenticationTrustResolver trustResolver = http
            .getSharedObject(AuthenticationTrustResolver.class);
      if (trustResolver != null) {
         defaultHandler.setTrustResolver(trustResolver);
      }
      ApplicationContext context = http.getSharedObject(ApplicationContext.class);
      if (context != null) {
         String[] roleHiearchyBeanNames = context.getBeanNamesForType(RoleHierarchy.class);
         if (roleHiearchyBeanNames.length == 1) {
            defaultHandler.setRoleHierarchy(context.getBean(roleHiearchyBeanNames[0], RoleHierarchy.class));
         }
         String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
         if (grantedAuthorityDefaultsBeanNames.length == 1) {
            GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
            defaultHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
         }
         String[] permissionEvaluatorBeanNames = context.getBeanNamesForType(PermissionEvaluator.class);
         if (permissionEvaluatorBeanNames.length == 1) {
            PermissionEvaluator permissionEvaluator = context.getBean(permissionEvaluatorBeanNames[0], PermissionEvaluator.class);
            defaultHandler.setPermissionEvaluator(permissionEvaluator);
         }
      }

      expressionHandler = postProcess(defaultHandler);
   }

   return expressionHandler;
}

getExpressionHandler这个方法是来确定表达式解析器和上下文的。因为Security的权限是有语法的,借用的是SpEL的语法,这里会设置一个AuthenticationTrustResolver,一个RoleHierarchy,一个GrantedAuthorityDefaults,一个PermissionEvaluator,后面三个只有在容器中有且只有一个才会生效,第一个只有在给HttpSecurityBuilder中设置了才有。

RoleHierarchy是用来设置role的大小比较的,GrantedAuthorityDefaults是修改权限的前缀的,不设置默认为ROLE_,PermissionEvaluator是解析表达式hasPermission()的结果的。

ExpressionInterceptUrlRegistry

ExpressionUrlAuthorizationConfigurer的内部类,通过继承关系,也知道这个类才是真正生成RequestMatcher的。

public class ExpressionInterceptUrlRegistry
      extends
      ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> {

   private ExpressionInterceptUrlRegistry(ApplicationContext context) {
      setApplicationContext(context);
   }

   @Override
   public MvcMatchersAuthorizedUrl mvcMatchers(HttpMethod method, String... mvcPatterns) {
      return new MvcMatchersAuthorizedUrl(createMvcMatchers(method, mvcPatterns));
   }

   @Override
   public MvcMatchersAuthorizedUrl mvcMatchers(String... patterns) {
      return mvcMatchers(null, patterns);
   }

   @Override
   protected final AuthorizedUrl chainRequestMatchersInternal(
         List<RequestMatcher> requestMatchers) {
      return new AuthorizedUrl(requestMatchers);
   }

   public ExpressionInterceptUrlRegistry expressionHandler(
         SecurityExpressionHandler<FilterInvocation> expressionHandler) {
      ExpressionUrlAuthorizationConfigurer.this.expressionHandler = expressionHandler;
      return this;
   }
    
   public ExpressionInterceptUrlRegistry withObjectPostProcessor(
         ObjectPostProcessor<?> objectPostProcessor) {
      addObjectPostProcessor(objectPostProcessor);
      return this;
   }

   public H and() {
      return ExpressionUrlAuthorizationConfigurer.this.and();
   }

}

继承AbstractInterceptUrlRegistry时第二个泛型参数为AuthorizedUrl,通过前面知道了,这个类型是在生成RequestMatcher之后用来继续调用链的类型。chainRequestMatchersInternal方法中就返回了这个类型的实例。

还定义了两个特殊的方法:expressionHandlerwithObjectPostProcessor

简单提下SecurityExpressionHandler,它有两个方法ExpressionParser getExpressionParser();EvaluationContext createEvaluationContext(Authentication authentication, T invocation);,其中ExpressionParser是对表达式进行解析的解析器,EvaluationContext是解析这个表达式所使用的上下文。

addObjectPostProcessorSecurityConfigurerAdapter的方法,ObjectPostProcessor是用来进行类初始化的,spring的容器强大之处就是类的初始化变得很简单,这里设置的初始化器在没有设置SecurityExpressionHandler时用来初始化默认的SecurityExpressionHandler的。

AuthorizedUrl

ExpressionUrlAuthorizationConfigurer的内部类。

看方法名就知道了,这是在设置权限要求,然后又回到了ExpressionInterceptUrlRegistry,可以继续设置其他权限要求。

随便看几个方法的实现:

private boolean not;

public AuthorizedUrl not() {
   this.not = true;
   return this;
}

public ExpressionInterceptUrlRegistry hasRole(String role) {
   return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}

public ExpressionInterceptUrlRegistry access(String attribute) {
   if (not) {
      attribute = "!" + attribute;
   }
   interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
   return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
}

这个ExpressionUrlAuthorizationConfigurer.hasRole(role)是这样的:

private static String hasRole(String role) {
   Assert.notNull(role, "role cannot be null");
   if (role.startsWith("ROLE_")) {
      throw new IllegalArgumentException(
            "role should not start with 'ROLE_' since it is automatically inserted. Got '"
                  + role + "'");
   }
   return "hasRole('ROLE_" + role + "')";
}

就是生成了一个字符串,security利用了SpEL的语法,在配置的时候,可以按照语法直接使用access方法来设置权限要求。

这个interceptUrl方法是ExpressionUrlAuthorizationConfigurer的方法,因为AuthorizedUrl是它的内部类,所以能直接使用:

private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
      Collection<ConfigAttribute> configAttributes) {
   for (RequestMatcher requestMatcher : requestMatchers) {
      REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
            requestMatcher, configAttributes));
   }
}

看到没,这里就调用了addMapping方法。


MethodSecurityInterceptor-方法级别的权限认证

认证的实现(用户认证部分)

授权的实现

spring security实现OAuth2的不止一个解决方案,有spring-cloud-oauth2提供的,也有sprnig-cloud-oauth2-client,sprnig-cloud-oauth2-resource-server提供的,官网的文档讲的是后者,而绝大部分博客和入门视频讲的是前者,因为前者是微服务使用的,后者是动态网站使用的。

快速上手

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Bean("jdbcClientDetailsService")
    public ClientDetailsService jdbcClientDetailsService() {
        JdbcClientDetailsService service = new JdbcClientDetailsService(dataSource);
        return service;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public AuthorizationServerTokenServices jdbcTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        // 超时时间,是否使用刷新令牌看的是clientDetailsService的
        tokenServices.setClientDetailsService(jdbcClientDetailsService());
        // 绑定tokenStore
        tokenServices.setTokenStore(new JdbcTokenStore(dataSource));
        // 开启支持刷新token
        tokenServices.setSupportRefreshToken(true);
        return tokenServices;
    }

    /**
     * 配置获取客户端授权信息为数据库方式
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(jdbcTokenServices())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }
}

@EnableAuthorizationServer

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {

}

这个注解把AuthorizationServerEndpointsConfiguration和AuthorizationServerSecurityConfiguration的实例加入到了容器中。

AuthorizationServerConfigurer

public interface AuthorizationServerConfigurer {

   /**
    * Configure the security of the Authorization Server, which means in practical terms the /oauth/token endpoint. The
    * /oauth/authorize endpoint also needs to be secure, but that is a normal user-facing endpoint and should be
    * secured the same way as the rest of your UI, so is not covered here. The default settings cover the most common
    * requirements, following recommendations from the OAuth2 spec, so you don't need to do anything here to get a
    * basic server up and running.
    * 
    * @param security a fluent configurer for security features
    */
   void configure(AuthorizationServerSecurityConfigurer security) throws Exception;

   /**
    * Configure the {@link ClientDetailsService}, e.g. declaring individual clients and their properties. Note that
    * password grant is not enabled (even if some clients are allowed it) unless an {@link AuthenticationManager} is
    * supplied to the {@link #configure(AuthorizationServerEndpointsConfigurer)}. At least one client, or a fully
    * formed custom {@link ClientDetailsService} must be declared or the server will not start.
    * 
    * @param clients the client details configurer
    */
   void configure(ClientDetailsServiceConfigurer clients) throws Exception;

   /**
    * Configure the non-security features of the Authorization Server endpoints, like token store, token
    * customizations, user approvals and grant types. You shouldn't need to do anything by default, unless you need
    * password grants, in which case you need to provide an {@link AuthenticationManager}.
    * 
    * @param endpoints the endpoints configurer
    */
   void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;

}

要实现授权中心的话,只需要实现这个接口并放到容器中就行了,和授权有关的配置就在这些接口方法中,需要配置的类会被当作参数传过来。

AuthorizationServerConfigurerAdapter

它是AuthorizationServerConfigurer的空方法继承:

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {

   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   }

   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   }

}

AuthorizationServerEndpointsConfiguration

@Configuration
@Import(TokenKeyEndpointRegistrar.class)
public class AuthorizationServerEndpointsConfiguration {
    
    private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
    
    @PostConstruct
	public void init() {
		for (AuthorizationServerConfigurer configurer : configurers) {
			try {
				configurer.configure(endpoints);
			} catch (Exception e) {
				throw new IllegalStateException("Cannot configure enpdoints", e);
			}
		}
		endpoints.setClientDetailsService(clientDetailsService);
	}
    
    @Bean
	public TokenEndpoint tokenEndpoint() throws Exception {
		TokenEndpoint tokenEndpoint = new TokenEndpoint();
		tokenEndpoint.setClientDetailsService(clientDetailsService);
		tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
		tokenEndpoint.setTokenGranter(tokenGranter());
		tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
		tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
		tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
		return tokenEndpoint;
	} 
}

这个类在构造之后初始化时会使用容器中所有的AuthorizationServerConfigurer给一个新建的AuthorizationServerEndpointsConfigurer添加配置,可以理解为全局配置。AuthorizationServerEndpointsConfigurer是个纯粹的属性类,用来保存配置的。

这个配置类创建了很多bean:

  • AuthorizationEndpoint
  • TokenEndpoint
  • CheckTokenEndpoint

分别对应/oauth/authorize/oauth/token/oauth/check_token,用来实现oauth2协议的

还用@Import创建了TokenKeyEndpointRegistrar,用来在使用了JWT时新增一个接口/oauth/token_key用来获取JWT签名的key。

AuthorizationServerSecurityConfiguration

@Configuration
@Order(0)
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
	private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

	@Autowired
	private ClientDetailsService clientDetailsService;
    
    @Autowired
	public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
		for (AuthorizationServerConfigurer configurer : configurers) {
			configurer.configure(clientDetails);
		}
	}
    
    @Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
		// This will ensure that when this configurer builds the AuthenticationManager it will not attempt
		// to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
		// and set that as the parent of this 'Local' AuthenticationManager.
		// This AuthenticationManager should only be wired up with an AuthenticationProvider
		// composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
	}
    
}

继承自WebSecurityConfigurerAdapter,是给WebSecurity进行配置的。

configure(ClientDetailsServiceConfigurer clientDetails)这个方法会自动执行,会使用容器中所有的AuthorizationServerConfigurer给容器中的ClientDetailsServiceConfigurer进行配置,容器中的ClientDetailsServiceConfigurer来自ClientDetailsServiceConfiguration,就是上面@Import的类。

特殊的,重写了configure(AuthenticationManagerBuilder auth),并没有执行任何语句,原因在注解里说了,为了不使用全局的AuthenticationManager,而使用这个配置类根据自己的配置生成的,为什么这么做参见AuthenticationManager的生成

@Override
protected void configure(HttpSecurity http) throws Exception {
    AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
    FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
    http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
    configure(configurer);
    http.apply(configurer);
    String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
    String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
    String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
    if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
        UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
        endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
    }
    // @formatter:off
    http
        .authorizeRequests()
            .antMatchers(tokenEndpointPath).fullyAuthenticated()
            .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
            .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
    .and()
    	.requestMatchers()
        	.antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
    .and()
    	.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
    // @formatter:on
    http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}

protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    for (AuthorizationServerConfigurer configurer : configurers) {
        configurer.configure(oauthServer);
    }
}

/oauth/token路径要求完整的用户认证,对/oauth/token_key/oauth/check_token的认证要求来自AuthorizationServerSecurityConfigurerAuthorizationServerSecurityConfigurer由容器中所有的AuthorizationServerConfigurer进行配置。至此,我们要写的AuthorizationServerConfigurer的三个接口方法全部出现了。

http.apply(configurer),这个AuthorizationServerSecurityConfigurer还会创建过滤器。

requestMatchers()配置了这个过滤器链的匹配器,只有这个三个路径的请求才走这个过滤器链。

最后在http中保存了个clientDetailsService

AuthorizationServerSecurityConfigurer

public final class AuthorizationServerSecurityConfigurer extends
      SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private String tokenKeyAccess = "denyAll()";
	private String checkTokenAccess = "denyAll()";
    public AuthorizationServerSecurityConfigurer tokenKeyAccess(String tokenKeyAccess) {
		this.tokenKeyAccess = tokenKeyAccess;
		return this;
	}
	public AuthorizationServerSecurityConfigurer checkTokenAccess(String checkTokenAccess) {
		this.checkTokenAccess = checkTokenAccess;
		return this;
	}
	public String getTokenKeyAccess() { return tokenKeyAccess; }
	public String getCheckTokenAccess() { return checkTokenAccess; }
    
    @Override
	public void init(HttpSecurity http) throws Exception {

		registerDefaultAuthenticationEntryPoint(http);
		if (passwordEncoder != null) {
			ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
			clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
			http.getSharedObject(AuthenticationManagerBuilder.class)
					.userDetailsService(clientDetailsUserDetailsService)
					.passwordEncoder(passwordEncoder());
		}
		else {
			http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
		}
		http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
				.httpBasic().realmName(realm);
		if (sslOnly) {
			http.requiresChannel().anyRequest().requiresSecure();
		}
	}
    
    private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) {
		ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http
				.getConfigurer(ExceptionHandlingConfigurer.class);
		if (exceptionHandling == null) {
			return;
		}
		if (authenticationEntryPoint==null) {
			BasicAuthenticationEntryPoint basicEntryPoint = new BasicAuthenticationEntryPoint();
			basicEntryPoint.setRealmName(realm);
			authenticationEntryPoint = basicEntryPoint;
		}
		ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
		if (contentNegotiationStrategy == null) {
			contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
		}
		MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
				MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON,
				MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA,
				MediaType.TEXT_XML);
		preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
		exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
	}
    
    @Override
	public void configure(HttpSecurity http) throws Exception {
		
		// ensure this is initialized
		frameworkEndpointHandlerMapping();
		if (allowFormAuthenticationForClients) {
			clientCredentialsTokenEndpointFilter(http);
		}

		for (Filter filter : tokenEndpointAuthenticationFilters) {
			http.addFilterBefore(filter, BasicAuthenticationFilter.class);
		}

		http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
	}
}

tokenKeyAccess和checkTokenAccess配置那两个端点的认证要求的,默认为denyAll,也就是禁了这两个端点,如果有需求可以通过AuthorizationServerConfigurer来配置。

init方法首先在异常拦截器(ExceptionTranslationFilter)中注册了一个AuthenticationEntryPoint,用来在认证失败的情况时通过各种方式通知请求方需要认证,默认情况使用BasicAuthenticationEntryPoint

public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint,
      InitializingBean {
   // ~ Instance fields
   // ================================================================================================

   private String realmName;

   // ~ Methods
   // ========================================================================================================

   public void afterPropertiesSet() {
      Assert.hasText(realmName, "realmName must be specified");
   }

   public void commence(HttpServletRequest request, HttpServletResponse response,
         AuthenticationException authException) throws IOException {
      response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
      response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
   }

   public String getRealmName() {
      return realmName;
   }

   public void setRealmName(String realmName) {
      this.realmName = realmName;
   }

}

这会在header中添加WWW-Authenticate,参考RFC2617了解具体是干什么的。

关注点在response.sendError上,当认证失败时会返回401错误码。

什么时候会触发这个方法呢?可以看到,这个AuthenticationEntryPoint是在ExceptionTranslationFilter中catch到异常时使用的,而ExceptionTranslationFilter在过滤器链的几乎最后面,它的后面一般就只有FilterSecurityInterceptor和Controller了,抛出异常的地方只有FilterSecurityInterceptor认证失败时的AccessDeniedException,和Controller的异常。先看FilterSecurityInterceptor中的异常,抛出这个异常的前提是AccessDecisionManager判定权限不足,而这些请求路径需要的权限的设置可以看到AuthorizationServerSecurityConfiguration中的configure方法:

http
    .authorizeRequests()
        .antMatchers(tokenEndpointPath).fullyAuthenticated()
        .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
        .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())

tokenEndpointPath即/oauth/token需要的权限是fullyAuthenticated,判定条件是:

public final boolean isFullyAuthenticated() {
   return !trustResolver.isAnonymous(authentication)
         && !trustResolver.isRememberMe(authentication);
}

只要用户认证的结果不是匿名或者记住我就行了,由于AuthorizationServerSecurityConfigurer中的init方法给过滤器链加上了个BasicAuthenticationFilter

http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
				.httpBasic().realmName(realm);

这个过滤器就是用来认证Basic用户的,一旦认证成功,那就不会是匿名或者记住我用户类型了。如果Basic认证失败,那就会变成匿名或者记住我用户,因为没有给配置其他用户认证的方式了,那么这样就会抛出异常导致401。

那么,这个Basic认证怎样才能认证成功呢?

.httpBasic()生成BasicAuthenticationFilter时,会将http中的AuthenticationManager作为构造参数之一,这个AuthenticationManager就是用来认证的了,这里的AuthenticationManager来自AuthorizationServerSecurityConfigurer中的init方法:

if (passwordEncoder != null) {
   ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
   clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
   http.getSharedObject(AuthenticationManagerBuilder.class)
         .userDetailsService(clientDetailsUserDetailsService)
         .passwordEncoder(passwordEncoder());
}
else {
   http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
}

userDetailsService这个方法被使用后,就会生成DaoAuthenticationProvider用来生成ProviderManager,这里的UserDetail就是容器中的ClientDetailsService,提前看下面就知道这是客户端认证所用的信息。所以如果客户端认证失败就会返回401。

关于userDetailsService怎么生成AuthenticationManager请看AuthenticationManager的生成

至于Controller的异常,这里的Controller是TokenEndpoint等,就是那三个路径:/oauth/token/oauth/authorize/oauth/check_token的控制器,抛出异常的情况就是授权失败的情况,有用户名密码错误,用户不存在,用户被禁用等。。详见TODO

ClientDetailsServiceConfiguration

@Configuration
public class ClientDetailsServiceConfiguration {

   @SuppressWarnings("rawtypes")
   private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
   
   @Bean
   public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
      return configurer;
   }

   @Bean
   @Lazy
   @Scope(proxyMode=ScopedProxyMode.INTERFACES)
   public ClientDetailsService clientDetailsService() throws Exception {
      return configurer.and().build();
   }

}

AuthorizationServerSecurityConfiguration导入的配置类,创建了一个ClientDetailsServiceConfigurer放到了容器中,然后通过懒加载用创建的这个configurer建造了个ClientDetailsService放到了容器中,前面看到的这个ClientDetailsService实例就是来自这里,懒加载的方式使得前面的配置可以提前拿到ClientDetailsService,让ClientDetailsService的配置(构造)和使用(引用)能同时进行。

ClientDetailsServiceConfigurer

public class ClientDetailsServiceConfigurer extends
      SecurityConfigurerAdapter<ClientDetailsService, ClientDetailsServiceBuilder<?>> {
    public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder<?> builder) {
		setBuilder(builder);
	}

	public ClientDetailsServiceBuilder<?> withClientDetails(ClientDetailsService clientDetailsService) throws Exception {
		setBuilder(getBuilder().clients(clientDetailsService));
		return this.and();
	}

	public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
		InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
		setBuilder(next);
		return next;
	}
	public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
		JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
		setBuilder(next);
		return next;
	}
}

重载了构造方法,使得必须要求和builder进行绑定。

默认这个类就一个实例,ClientDetailsServiceConfiguration中创建的,被加到了容器中,在AuthorizationServerSecurityConfigurationconfigure(ClientDetailsServiceConfigurer clientDetails)方法中被调用来配置,最终是由我们写的AuthorizationServerConfigurerAdapter来配置的,能使用到的配置的方法就只有withClientDetails()inMemory()jdbc(DataSource)这三个。且这三个方法其实是互斥的,只能选择其一,因为这三个方法中都调用了setBuilder()更换了绑定的构造器,且如果点进去查看新构造器的来源,会发现全是new出来的全新的构造器,也就是说只要换一个方法,前面方法绑定的构造器就会被替换掉了。

ClientDetailsServiceBuilder

这个类是用来生成 保存 客户端认证的客户端账号信息 的服务。

public class ClientDetailsServiceBuilder<B extends ClientDetailsServiceBuilder<B>> extends
      SecurityConfigurerAdapter<ClientDetailsService, B> implements SecurityBuilder<ClientDetailsService> {

    private List<ClientBuilder> clientBuilders = new ArrayList<ClientBuilder>();

	public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
		return new InMemoryClientDetailsServiceBuilder();
	}

	public JdbcClientDetailsServiceBuilder jdbc() throws Exception {
		return new JdbcClientDetailsServiceBuilder();
	}

	@SuppressWarnings("rawtypes")
	public ClientDetailsServiceBuilder<?> clients(final ClientDetailsService clientDetailsService) throws Exception {
		return new ClientDetailsServiceBuilder() {
			@Override
			public ClientDetailsService build() throws Exception {
				return clientDetailsService;
			}
		};
	}

    public ClientBuilder withClient(String clientId) {
		ClientBuilder clientBuilder = new ClientBuilder(clientId);
		this.clientBuilders.add(clientBuilder);
		return clientBuilder;
	}

	@Override
	public ClientDetailsService build() throws Exception {
		for (ClientBuilder clientDetailsBldr : clientBuilders) {
			addClient(clientDetailsBldr.clientId, clientDetailsBldr.build());
		}
		return performBuild();
	}

	protected void addClient(String clientId, ClientDetails build) {
	}

	protected ClientDetailsService performBuild() {
		throw new UnsupportedOperationException("Cannot build client services (maybe use inMemory() or jdbc()).");
	}
}

这个类是构造ClientDetailsService的类,看到performBuild()方法,会抛出异常,可以知道必须使用这个类的子类来重写这个方法才行。默认有两个子类:

  • InMemoryClientDetailsServiceBuilder
  • JdbcClientDetailsServiceBuilder

inMemory()jdbc()也会返回这两个子类的实例,实际上clients()方法也是返回子类的实例,不过这个子类是匿名类。

InMemoryClientDetailsServiceBuilder子类会创建一个客户端用户数据保存在内存中的ClientDetailsServiceInMemoryClientDetailsServiceInMemoryClientDetailsService使用Map来保存所有客户端。可以通过addClient()直接添加或者withClient()开启一个客户端用户的构造来添加,来设置ClientDetailsService中保存的所有客户端。

JdbcClientDetailsServiceBuilder子类会将创建一个客户端用户数据保存在数据库中的ClientDetailsServiceJdbcClientDetailsServiceJdbcClientDetailsService使用DataSource连接数据库来保存数据,可以通过addClient()直接添加或者withClient()开启一个客户端用户的构造来添加,来设置ClientDetailsService中保存的所有客户端,最终会通过设置好的SQL语句来读写数据库。

JdbcClientDetailsService限制死了数据库的SQL语句,也就是说必须使用规定的表定义来保存数据,如果想加一些其他数据在表里的话,比如客户端余额什么的,可以直接写ClientDetailsService的实现类,然后通过clients()方法设置。

TokenEndpoint

提供/oauth/token端点的类。

@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {

   private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();

   private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));

   @RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
   public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam
   Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
      if (!allowedRequestMethods.contains(HttpMethod.GET)) {
         throw new HttpRequestMethodNotSupportedException("GET");
      }
      return postAccessToken(principal, parameters);
   }
   
   @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
   public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
   Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

      if (!(principal instanceof Authentication)) {
         throw new InsufficientAuthenticationException(
               "There is no client authentication. Try adding an appropriate authentication filter.");
      }

      String clientId = getClientId(principal);
      ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

      TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

      if (clientId != null && !clientId.equals("")) {
         // Only validate the client details if a client authenticated during this
         // request.
         if (!clientId.equals(tokenRequest.getClientId())) {
            // double check to make sure that the client ID in the token request is the same as that in the
            // authenticated client
            throw new InvalidClientException("Given client ID does not match authenticated client");
         }
      }
      if (authenticatedClient != null) {
         oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
      }
      if (!StringUtils.hasText(tokenRequest.getGrantType())) {
         throw new InvalidRequestException("Missing grant type");
      }
      if (tokenRequest.getGrantType().equals("implicit")) {
         throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
      }

      if (isAuthCodeRequest(parameters)) {
         // The scope was requested or determined during the authorization step
         if (!tokenRequest.getScope().isEmpty()) {
            logger.debug("Clearing scope of incoming token request");
            tokenRequest.setScope(Collections.<String> emptySet());
         }
      }

      if (isRefreshTokenRequest(parameters)) {
         // A refresh token has its own default scopes, so we should ignore any added by the factory here.
         tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
      }

      OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
      if (token == null) {
         throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
      }

      return getResponse(token);

   }

   /**
    * @param principal the currently authentication principal
    * @return a client id if there is one in the principal
    */
   protected String getClientId(Principal principal) {
      Authentication client = (Authentication) principal;
      if (!client.isAuthenticated()) {
         throw new InsufficientAuthenticationException("The client is not authenticated.");
      }
      String clientId = client.getName();
      if (client instanceof OAuth2Authentication) {
         // Might be a client and user combined authentication
         clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();
      }
      return clientId;
   }

   private boolean isRefreshTokenRequest(Map<String, String> parameters) {
      return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
   }

   private boolean isAuthCodeRequest(Map<String, String> parameters) {
      return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
   }

   public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) {
      this.oAuth2RequestValidator = oAuth2RequestValidator;
   }

   public void setAllowedRequestMethods(Set<HttpMethod> allowedRequestMethods) {
      this.allowedRequestMethods = allowedRequestMethods;
   }
}

以上代码删掉了异常处理,异常处理之后讲。

没有使用@Controller而是@FrameworkEndpoint是因为允许用户设置请求前缀,所以最终的端点其实是这样的:/前缀/oauth/token

可以看到处理请求的流程是这样的:

  1. 使用OAuth2RequestFactory根据请求参数和客户端信息生成获取token请求
  2. 确认clientId一致
  3. 判断认证客户端作用域有效
  4. 清除授权码模式下的作用域参数(scope),oauth2协议中说授权码模式中scope参数是可选的,这里security让它统统为默认值了。
  5. 如果是刷新token请求,scope必须设置为请求中携带的
  6. 最后,通过TokenGranter授权token
  7. 如果token为空,证明授权失败,抛出异常
  8. 如果非空,返回格式化的结果(json)

最重要的一步就是TokenGranter授权了,TokenGranter是在AuthorizationServerEndpointsConfigurationtokenEndpoint()方法构造TokenEndpoint时传进去的:

public class AuthorizationServerEndpointsConfiguration {

	private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();

    @Bean
    public TokenEndpoint tokenEndpoint() throws Exception {
        TokenEndpoint tokenEndpoint = new TokenEndpoint();
        ...
        tokenEndpoint.setTokenGranter(tokenGranter());
        ...
        return tokenEndpoint;
    }

    private TokenGranter tokenGranter() throws Exception {
        return getEndpointsConfigurer().getTokenGranter();
    }

    public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() {
        if (!endpoints.isTokenServicesOverride()) {
            try {
                endpoints.tokenServices(endpoints.getDefaultAuthorizationServerTokenServices());
            }
            catch (Exception e) {
                throw new BeanCreationException("Cannot create token services", e);
            }
        }
        return endpoints;
    }
}
public final class AuthorizationServerEndpointsConfigurer {
    public TokenGranter getTokenGranter() {
		return tokenGranter();
	}

    private TokenGranter tokenGranter() {
		if (tokenGranter == null) {
			tokenGranter = new TokenGranter() {
				private CompositeTokenGranter delegate;

				@Override
				public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
					if (delegate == null) {
						delegate = new CompositeTokenGranter(getDefaultTokenGranters());
					}
					return delegate.grant(grantType, tokenRequest);
				}
			};
		}
		return tokenGranter;
	}

    private List<TokenGranter> getDefaultTokenGranters() {
		ClientDetailsService clientDetails = clientDetailsService();
		AuthorizationServerTokenServices tokenServices = tokenServices();
		AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
		OAuth2RequestFactory requestFactory = requestFactory();

		List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
		tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
				requestFactory));
		tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
		ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
		tokenGranters.add(implicit);
		tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
		if (authenticationManager != null) {
			tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
					clientDetails, requestFactory));
		}
		return tokenGranters;
	}

    public AuthorizationServerTokenServices getDefaultAuthorizationServerTokenServices() {
		if (defaultTokenServices != null) {
			return defaultTokenServices;
		}
		this.defaultTokenServices = createDefaultTokenServices();
		return this.defaultTokenServices;
	}

    private DefaultTokenServices createDefaultTokenServices() {
		DefaultTokenServices tokenServices = new DefaultTokenServices();
		tokenServices.setTokenStore(tokenStore());
		tokenServices.setSupportRefreshToken(true);
		tokenServices.setReuseRefreshToken(reuseRefreshToken);
		tokenServices.setClientDetailsService(clientDetailsService());
		tokenServices.setTokenEnhancer(tokenEnhancer());
		addUserDetailsService(tokenServices, this.userDetailsService);
		return tokenServices;
	}

    private TokenStore tokenStore() {
		if (tokenStore == null) {
			if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
				this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
			}
			else {
				this.tokenStore = new InMemoryTokenStore();
			}
		}
		return this.tokenStore;
	}
}

如果没有设置AuthorizationServerTokenServices,就会生成一个默认的DefaultTokenServices;如果没有设置TokenGranter,也会生成许多自带的TokenGranter,看名字就猜出来了,每一个TokenGranter对应一种授权方法(授权码,简化,密码,客户端,刷新);如果没有设置TokenStore,也会生成默认的TokenStore,会根据AccessTokenConverter来生成,默认的AccessTokenConverterDefaultAccessTokenConverter,下面就来讲每一部分是干什么的。

TokenGranter

public interface TokenGranter {

   OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);

}

根据请求来授予token,有一个实现类CompositeTokenGranter,内部保存多种TokenGranter,每种TokenGranter能够处理一种grant_type类型,CompositeTokenGranter把请求依次交给保存的TokenGranter去处理,直到一个TokenGranter能够处理这种类型的token请求,来得到结果。

TokenGranter会使用AuthorizationServerTokenServices来生成token。

AuthorizationServerTokenServices

授权服务的token服务,整个授权部分分发token的最高级部分,只抽象出了三个接口:

public interface AuthorizationServerTokenServices {

   /**
    * Create an access token associated with the specified credentials.
    * @param authentication The credentials associated with the access token.
    * @return The access token.
    * @throws AuthenticationException If the credentials are inadequate.
    */
   OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;

   /**
    * Refresh an access token. The authorization request should be used for 2 things (at least): to validate that the
    * client id of the original access token is the same as the one requesting the refresh, and to narrow the scopes
    * (if provided).
    * 
    * @param refreshToken The details about the refresh token.
    * @param tokenRequest The incoming token request.
    * @return The (new) access token.
    * @throws AuthenticationException If the refresh token is invalid or expired.
    */
   OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
         throws AuthenticationException;

   /**
    * Retrieve an access token stored against the provided authentication key, if it exists.
    * 
    * @param authentication the authentication key for the access token
    * 
    * @return the access token or null if there was none
    */
   OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}

调用对应的方法,可以直接获取到一个token,或者得到个异常,隐藏了底下的token的生成,token的保存等一系列操作。

AuthorizationServerTokenServices会使用TokenStore来进行token的保存,token的获取,token的生成等操作。

TokenStore

public interface TokenStore {
    OAuth2Authentication readAuthentication(OAuth2AccessToken token);
    OAuth2Authentication readAuthentication(String token);
    void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
    OAuth2AccessToken readAccessToken(String tokenValue);
    void removeAccessToken(OAuth2AccessToken token);
    void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);
    OAuth2RefreshToken readRefreshToken(String tokenValue);
    OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);
    void removeRefreshToken(OAuth2RefreshToken token);
    void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken);
    OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
    Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);
    Collection<OAuth2AccessToken> findTokensByClientId(String clientId);
}

对应了token的增删改查,token实际上是用户信息的一种表示,它能代表一个经过认证的用户,通过它能获取用户的信息,通过用户的信息也能获取它。有些token中直接保存了用户的信息,这种信息之间的转换就是通过AccessTokenConverter做到的。

AccessTokenConverter

public interface AccessTokenConverter {

   final String AUD = "aud";

   final String CLIENT_ID = "client_id";

   final String EXP = "exp";

   final String JTI = "jti";
   
   final String GRANT_TYPE = "grant_type";

   final String ATI = "ati";

   final String SCOPE = OAuth2AccessToken.SCOPE;

   final String AUTHORITIES = "authorities";

   /**
    * @param token an access token
    * @param authentication the current OAuth authentication
    * 
    * @return a map representation of the token suitable for a JSON response
    * 
    */
   Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);

   /**
    * Recover an access token from the converted value. Half the inverse of
    * {@link #convertAccessToken(OAuth2AccessToken, OAuth2Authentication)}.
    * 
    * @param value the token value
    * @param map information decoded from an access token
    * @return an access token
    */
   OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);

   /**
    * Recover an {@link OAuth2Authentication} from the converted access token. Half the inverse of
    * {@link #convertAccessToken(OAuth2AccessToken, OAuth2Authentication)}.
    * 
    * @param map information decoded from an access token
    * @return an authentication representing the client and user (if there is one)
    */
   OAuth2Authentication extractAuthentication(Map<String, ?> map);
}

token和用户信息之间的一种转换器,有些token可以保存用户信息,就可以通过AccessTokenConverter来“序列化”这些信息,也可以”反序列化“成用户的原始信息。

其他

AuthenticationManager的生成

Q.E.D.


悟已往之不谏,知来者之可追