spring security oauth2 2.1.x

spring security oauth2 是基于 spring security,所以要理解 oauth2 是需要有 spring security 的基础。oauth2 包含的主要角色有:认证服务器、资源服务器。

1、认证服务器的示例

通过一个注解就可以表明当前服务是认证服务器:

@Configuration
@EnableAuthorizationServer

进入注解 EnableAuthorizationServer,可以看到通过 @Import 引入两个重要的配置类

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

其中

暴露接口的类:AuthorizationServerEndpointsConfiguration,如,/oauth/authorize 的 AuthorizationEndpoint、/oauth/token 的 TokenEndpoint、/oauth/check_token 的 CheckTokenEndpoint

对各种接口配置权限、设置客户端的存储方式:AuthorizationServerSecurityConfiguration。

分析AuthorizationServerEndpointsConfiguration:

@Configuration
@Import(TokenKeyEndpointRegistrar.class)
public class AuthorizationServerEndpointsConfiguration {

	// 认证服务器接口的配置
	private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();

	// 客户端信息服务,默认是 InMemoryClientDetailsService,一般重写为 JdbcClientDetailsService
	@Autowired
	private ClientDetailsService clientDetailsService;

	// 默认没有实现,需要自己实现。所以注解 @Configuration、@EnableAuthorizationServer 就可以放在这个实现类上。
	@Autowired
	private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

	@PostConstruct
	public void init() {
		for (AuthorizationServerConfigurer configurer : configurers) {
			try {
				// 给 endpoints 设置自定义的配置。
				configurer.configure(endpoints);
			} catch (Exception e) {
				throw new IllegalStateException("Cannot configure enpdoints", e);
			}
		}

		// 设置回配置
		endpoints.setClientDetailsService(clientDetailsService);
	}

	// 1、 AuthorizationEndpoint 创建
	// AuthorizationEndpoint 用于服务于授权请求。预设地址:/oauth/authorize。
	@Bean
	public AuthorizationEndpoint authorizationEndpoint() throws Exception {
		AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
		FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
		authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
		authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
		authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
		authorizationEndpoint.setTokenGranter(tokenGranter());
		authorizationEndpoint.setClientDetailsService(clientDetailsService);
		authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
		authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
		authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
		authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
		return authorizationEndpoint;
	}

	// 2、 TokenEndpoint 创建
	// TokenEndpoint 用于创建服务访问令牌的请求。预设地址:/oauth/token。
	@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;
	}

	// 3、 CheckTokenEndpoint 创建
	// CheckTokenEndpoint 用于查看服务访问令牌的请求。预设地址:/oauth/check_token。
	@Bean
	public CheckTokenEndpoint checkTokenEndpoint() {
		CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
		endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
		endpoint.setExceptionTranslator(exceptionTranslator());
		return endpoint;
	}

    // 省略部分代码
}

上面分析到实现 AuthorizationServerConfigurer,所以需要自定义,如下:


@Slf4j
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    // 默认 InMemoryClientDetailsService
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
        // 使用数据库实现,就要准备 oauth 的9张表
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        //客户端配置
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        //支持令牌的刷新
        defaultTokenServices.setSupportRefreshToken(true);
        //令牌存储服务
        defaultTokenServices.setTokenStore(tokenStore); JSONObject.toJSONString(jwtAccessTokenConverter));JSONObject.toJSONString(jwtAccessTokenConverter.getClass().getName()));
        //使用令牌增强
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter);//这里是生成token时会用到jwtAccessTokenConverter,在tokenConfig里面是验证token时用到
        //设置 access_token 的过期时间,2小时。默认12小时。
        defaultTokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
        //设置refresh_token的过期时间,30天。默认30天。
        defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
        return defaultTokenServices;
    }

    // AuthorizationServerEndpointsConfiguration 的 init() 调用,设置自定义的配置。
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .tokenServices(tokenService())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    // 开放 /oauth/token_key 和 /oauth/check_token
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

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

上面有用到 AuthenticationManager,而容器中不存在,所以需要注册一个,这里继承 WebSecurityConfigurerAdapter,同时拦截部分自定义接口,默认这些接口都是放开的,但是我们可以只开发部分接口(如,我们的登录接口)。


@Configuration
public class SecurityAuthorizationConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().and().cors().disable().csrf().disable()
                // 默认这些接口都是开放的,但是我们可以只开发部分接口(如,我们的登录接口)
                .authorizeRequests()
                // 使用 antMatchers,就会添加 AntPathRequestMatcher 路径匹配,默认是 AnyRequestMatcher(任何请求都返回 true,也就是开放)
                .antMatchers("/api/mylogin").permitAll()
                .antMatchers("/**").authenticated();
    }

    // 容器中还没有 AuthenticationManager 这个 bean,直接使用 @Bean 将父类的 AuthenticationManager 注入到容器。
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

在spring security oauth2.0 中,每次访问接口都需要经历 spring security 的过滤器链FilterChainProxy,这个过滤器链也是 spring security 的精髓,理解了这些过滤器,也就将 spring security oauth2.0 掌握了大半。

还是把Import 的两个配置类分析完,分析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
	private AuthorizationServerEndpointsConfiguration endpoints;

	@Autowired
	public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
		for (AuthorizationServerConfigurer configurer : configurers) {
            // 就是自定义 AuthorizationServerConfig 中给客户端服务设置存储方式,这里我们是 jdbc 方式
			configurer.configure(clientDetails);
		}
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		
	}

    // 可以看到,这里给 /oauth/token 默认设置需要全部权限、/oauth/token_key 和 /oauth/check_token 是拒绝访问。而自定义 AuthorizationServerConfig 中可以开放/oauth/token_key 和 /oauth/check_token。
	@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);
		}
		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);
		http.setSharedObject(ClientDetailsService.class, clientDetailsService);
	}

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

}

2、源码

下面从源码的角度分析 spring security。熟悉 spring boot 源码的都知道,要整合一个框架是通过自动配置,所以我们也查看 spring.factories,找到 spring security 自动配置类SecurityAutoConfiguration置类

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, 
        WebSecurityEnablerConfiguration.class, // 注意这个类,我们只看这个
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity    // 注意这个类,我们只看这个
public class WebSecurityEnablerConfiguration {

}

@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 {
	boolean debug() default false;
}

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)        // 目的就是引入 AuthenticationConfiguration
@Configuration
public @interface EnableGlobalAuthentication {
}

找到了一个重要的配置类 WebSecurityConfiguration 和 AuthenticationConfiguration。

2.1 分析 WebSecurityConfiguration

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			// 不存在 webSecurityConfigurers 就创建一个 WebSecurityConfigurerAdapter,一般我们都会实现,
			// 因为 WebSecurityConfigurerAdapter 的 configure(HttpSecurity http) 拦截所有接口,所有接口都需要认证,而我们需要开发登录接口,所以需要重写
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {});
			webSecurity.apply(adapter);
		}

		// (重要)构建 webSecurity 的过滤器链
		return webSecurity.build();
	}

    // 将容器中所有 SecurityConfigurer 实现到加入到 webSecurity,如我们自定义的 SecurityAuthorizationConfig
    @Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")
	List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
		webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}

		webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

		Integer previousOrder = null;
		Object previousConfig = null;
		for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException(
						"@Order on WebSecurityConfigurers must be unique. Order of "
								+ order + " was already used on " + previousConfig + ", so it cannot be used on "
								+ config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {

			// 将 webSecurityConfigurers 都加入到 webSecurity
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}
}

上面的属性注入肯定比内部 @Bean 要先,所以先找到所有 SecurityConfigurer,并加入到 webSecurity,默认就存在一个注入到容器的实现 AuthorizationServerSecurityConfiguration,每一个实现,都会构建一个 HttpSecurity,并且对应一个过滤器链。

注意这一行代码:webSecurity.build(),这一行代码就会去构建整个 security 的过滤器。

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

// 该方法会多次进入,WebSecurity、HttpSecurity、AuthenticationManagerBuilder等依次进入。
@Override
protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			// 执行 init() 方法,
			// 调用所有的 SecurityConfigurer 方法,其中包含 AuthorizationServerSecurityConfiguration 和 自定义的实现
			// 父类 WebSecurityConfigurerAdapter 才实现 init(),会构建 HttpSecurity
			init();

			buildState = BuildState.CONFIGURING;

			// WebSecurity 的 beforeConfigure() 空方法。
			// HttpSecurity 的 beforeConfigure() 是先构建 AuthenticationManagerBuilder 认证集合相关的 ProviderManager(实际认证的工作由 AuthenticationProvider 来做)。
			beforeConfigure();

            // HttpSecurity 的 configure() 会加载各种过滤器(比如,CorsConfigurer 就会给 HttpSecurity 添加 CorsFilter),然后在 performBuild() 构建过滤器链
			configure();

			buildState = BuildState.BUILDING;

			// 在 WebSecurity 的 performBuild() 方法中,会调用 HttpSecurity 的 build() 方法,然后进入 HttpSecurity 的 doBuild()。
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
}
过滤器的加载是由 HttpSecurity 添加的,比如 http.formLogin().and().cors().disable().csrf() 这一句就加入了 3 个过滤器,如 formLogin() 就会添加 UsernamePasswordAuthenticationFilter:
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
}

public FormLoginConfigurer() {
		super(new UsernamePasswordAuthenticationFilter(), null);
		usernameParameter("username");
		passwordParameter("password");
}

下面依次看看WebSecurity、HttpSecurity、AuthenticationManagerBuilder 的几个方法

1、WebSecurity 的 performBuild() 方法
@Override
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) {
			// 调用 HttpSecurity 的 build() 方法,然后进入 HttpSecurity 的 doBuild()。
			// 方法的返回值就是一个过滤器链
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		// 所以 FilterChainProxy 包含多个过滤器链
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

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

2、HttpSecurity 的 configure() 方法
@Override
protected void beforeConfigure() throws Exception {
		
		// 再次进入 AbstractConfiguredSecurityBuilder#build() 方法,
		// 构建 AuthenticationManagerBuilder 认证集合相关的 ProviderManager(实际认证的工作由 AuthenticationProvider 来做)。
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}

3、AuthenticationManagerBuilder的 performBuild() 方法
@Override
protected ProviderManager performBuild() throws Exception {
		if (!isConfigured()) {
			logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
			return null;
		}

		// 1、创建了一个包含 authenticationProviders 参数的 ProviderManager 对象
		// ProviderManager 是 AuthenticationManager 的实现类
		ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager);
		if (eraseCredentials != null) {
			// 在认证后,删除认证信息
			providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
		}
		if (eventPublisher != null) {
			// 认证成功后,发布事件
			providerManager.setAuthenticationEventPublisher(eventPublisher);
		}
		providerManager = postProcess(providerManager);
		return providerManager;
}


4、HttpSecurity 的 configure() 方法
private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {

            // 以 CorsConfigurer 为例
			configurer.configure((B) this);
		}
}

CorsConfigurer 的 configure() 方法
@Override
public void configure(H http) {
		ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        // CorsConfigurer 构建一个 CorsFilter
		CorsFilter corsFilter = getCorsFilter(context);
		if (corsFilter == null) {
			throw new IllegalStateException(
					"Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
							+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
		}
        
        // 添加进 HttpSecurity
		http.addFilter(corsFilter);
}

5、HttpSecurity 的 performBuild() 方法
@Override
protected DefaultSecurityFilterChain performBuild() {
		// 先将 filters 按照 order 排序,小的先执行
		filters.sort(comparator);

		// 构建过滤器链
		return new DefaultSecurityFilterChain(requestMatcher, filters);
}

所以一个 WebSecurity 包含多个 HttpSecurity;一个 HttpSecurity 包含一个过滤器链和一个认证管理器 AuthenticationManager;一个 AuthenticationManager 包含一个 ProviderManager,一个 ProviderManager 中真正参与认证类是一组 AuthenticationProvider。

过滤器链是什么时候执行的呢?

如果存在 SecurityFilterAutoConfiguration ,就会注入 DelegatingFilterProxyRegistrationBean

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)

// 条件是存在以下两个类
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}
}

而  DelegatingFilterProxyRegistrationBean 实现 ServletContextInitializer,在 Tomcat 构建 容器 ServletWebServerApplicationContext 时,就会调用当前 DelegatingFilterProxyRegistrationBean 的 getFilter 方法。

ServletWebServerApplicationContext 类 selfInitialize() 方法

private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
}

ServletWebServerApplicationContext#selfInitialize() ->
RegistrationBean#onStartup() ->
AbstractFilterRegistrationBean#getDescription() ->
DelegatingFilterProxyRegistrationBean#getFilter()

就把名称为 FilterChainProxy 的过滤器链加入到 Tomcat 的 Context 容器,在客户端调用服务器接口的时候,就会经历各种过滤器。

过滤器链中到底有多少个过滤器,有默认的过滤器,也有自定义添加的过滤器。先看默认的过滤器,在 WebSecurity 的 doBuild() 方法调用 init() 方法,遍历执行 SecurityConfigurer 的 init() 方法,也就是默认的 AuthorizationServerSecurityConfiguration 和我们实现的 SecurityAuthorizationConfig,init() 在它们的父类 WebSecurityConfigurerAdapter:

public void init(final WebSecurity web) throws Exception {

		// FIXME 构建 HttpSecurity
		final HttpSecurity http = getHttp();

		// HttpSecurity 存入到 WebSecurity 的 securityFilterChainBuilders
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
			FilterSecurityInterceptor securityInterceptor = http
					.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		});
}


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

		// FIXME 构建 HttpSecurity
		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		if (!disableDefaults) {
			// @formatter:off
			// 默认添加以下 Filter,重写 configure(http); 方法,可以增加自定义的过滤器
			http
					// 添加配置器 CsrfConfigurer(包含过滤器 CsrfFilter)对CSRF的支持。
					.csrf().and()
					// 添加过滤器 WebAsyncManagerIntegrationFilter。
					.addFilter(new WebAsyncManagerIntegrationFilter())
					// 添加配置器 ExceptionHandlingConfigurer(包含过滤器ExceptionTranslationFilter)对异常处理的支持。
					.exceptionHandling().and()
					// 添加配置器 HeadersConfigurer(包含过滤器HeaderWriterFilter)支持Adds the Security HTTP headers to the response。
					.headers().and()
					// 添加配置器SessionManagementConfigurer(包含过滤器SessionManagementFilter)支持session管理。
					.sessionManagement().and()
					// 添加配置器SecurityContextConfigurer(包含过滤器 SecurityContextPersistenceFilter)支持对SecurityContextHolder的配置。
					.securityContext().and()
					// 添加配置器RequestCacheConfigurer(包含过滤器RequestCacheAwareFilter)支持request cache。
					.requestCache().and()
					// 添加配置器AnonymousConfigurer(包含过滤器AnonymousAuthenticationFilter)支持Anonymous authentication。
					.anonymous().and()
					// 添加配置器ServletApiConfigurer(包含过滤器SecurityContextHolderAwareRequestFilter)支持更多Servlet API。
					.servletApi().and()
					// 添加配置器DefaultLoginPageConfigurer(包含过滤器DefaultLoginPageGeneratingFilter,DefaultLogoutPageGeneratingFilter)支持默认的login和logout。
					.apply(new DefaultLoginPageConfigurer<>()).and()
					// 添加配置器LogoutConfigurer(包含过滤器LogoutFilter)支持logout。
					.logout();
			// @formatter:on
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

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

		// FIXME HttpSecurity 拦截所有接口,即所有接口都需要认证,我们常常重写该方法,开发登录接口
		configure(http);
		return http;
}
AuthorizationServerSecurityConfiguration 的 configure(http); 实现如下
@Override
	protected void configure(HttpSecurity http) throws Exception {
		// AuthorizationServerSecurityConfigurer 会增加两个 Filter:
		// ClientCredentialsTokenEndpointFilter 和 BasicAuthenticationFilter
		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() 给过滤器链加上最后一个过滤器 FilterSecurityInterceptor
        	.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);
	}

所以,默认的过滤器链有以下过滤器:

WebAsyncManagerIntegrationFilter、SecurityContextPersistenceFilter、HeaderWriterFilter、LogoutFilter、
ClientCredentialsTokenEndpointFilter(oauth 提供不用,被后面 Basic 替换)、BasicAuthenticationFilter(/oauth/token)、
DefaultLoginPageGeneratingFilter、DefaultLogoutPageGeneratingFilter、RequestCacheAwareFilter、SecurityContextHolderAwareRequestFilter、
AnonymousAuthenticationFilter、SessionManagementFilter、ExceptionTranslationFilter、FilterSecurityInterceptor

而我们自定义的实现:

@Override
protected void configure(HttpSecurity http) throws Exception {
        http
                // 添加 UsernamePasswordAuthenticationFilter
                .formLogin()
                .and().cors().disable().csrf().disable()
                // 默认这些接口都是开放的,但是我们可以只开发部分接口(如,我们的登录接口)
                // authorizeRequests() 给过滤器链加上最后一个过滤器 FilterSecurityInterceptor
                .authorizeRequests()
                // 使用 antMatchers,就会添加 AntPathRequestMatcher 路径匹配,默认是 AnyRequestMatcher(任何请求都返回 true,也就是开放)
                .antMatchers("/api/mylogin").permitAll()
                //.antMatchers("/oauth**").permitAll()
                .antMatchers("/**").authenticated();
}

过滤器链有以下过滤器:

WebAsyncManagerIntegrationFilter、SecurityContextPersistenceFilter、HeaderWriterFilter、LogoutFilter、
UsernamePasswordAuthenticationFilter(HttpSecurity.formLogin() /login POST)、
DefaultLoginPageGeneratingFilter、DefaultLogoutPageGeneratingFilter、RequestCacheAwareFilter、SecurityContextHolderAwareRequestFilter、
AnonymousAuthenticationFilter、SessionManagementFilter、ExceptionTranslationFilter、FilterSecurityInterceptor

这里只分析 4 个过滤器:UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、ExceptionTranslationFilter、FilterSecurityInterceptor。

1、UsernamePasswordAuthenticationFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		// 1、判断请求地址是否是 /login 和 请求方式为 POST  (UsernamePasswordAuthenticationFilter 构造方法 确定的)
		// 当然可以通过 loginProcessingUrl() 修改 UsernamePasswordAuthenticationFilter 指定的 /login 为其它的,如 /user/auth/login
		if (!requiresAuthentication(request, response)) {
			// 如果不是 /login,那么就直接放行。我们一般都有自己的接口,如 /user/auth/login,这就不和 /login 一致,那么会在 AnonymousAuthenticationFilter 创建一个匿名认证
			chain.doFilter(request, response);

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Request is to process authentication");
		}

		Authentication authResult;

		try {
			// 2、调用子类 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法进行认证
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}

			// 3、将认证成功的 Authentication 存入Session中
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			// 4、认证失败
			// 调用 AuthenticationFailureHandler 的 onAuthenticationFailure() 方法进行失败处理
			// (可以通过继承 AuthenticationFailureHandler 自定义失败处理逻辑,比如我们是前后端分离,需要返回 json)
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		catch (AuthenticationException failed) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// Authentication success
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}

		// 5、认证成功
		// 调用 AuthenticationSuccessHandler 的 onAuthenticationSuccess() 进行失败处理
		// (可以通过继承 AuthenticationFailureHandler 自定义失败处理逻辑,比如我们是前后端分离,需要返回 json,但是成功我们都是返回 accessToken,所以不用管)
		successfulAuthentication(request, response, chain, authResult);
}

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();

		// 封装 username、password,此时 token 还属于未认证状态
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		// 通过 认证管理器进行认证。
		// AuthenticationManager 的实现是 ProviderManager
		return this.getAuthenticationManager().authenticate(authRequest);
}


// ProviderManager 的 authenticate()
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 入参是未认证的 Token,出参是认证成功的 Token

		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

		// 1、通过 getProviders() 方法获取 AuthenticationProvider 集合
		for (AuthenticationProvider provider : getProviders()) {
			// Authentication 的实现都是各种类型的 Token,比如 UsernamePasswordAuthenticationToken
			// 一种 token 可能对应多个 AuthenticationProvider,比如支持 UsernamePasswordAuthenticationToken 的有 AbstractLdapAuthenticationProvider、AbstractUserDetailsAuthenticationProvider
			// 但是同时只会有一个支持的
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				// FIXME 3、调用认证方法
				// 看实现 DaoAuthenticationProvider(父类是 AbstractUserDetailsAuthenticationProvider)
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				// 4、前面都认证不成功,调用父类(严格意思不是调用父类,而是其他的 AuthenticationManager 实现类)认证方法
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data from authentication
				// 5、删除认证成功后的 密码信息,保证安全
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
}


// AbstractUserDetailsAuthenticationProvider 的 authenticate()
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		// 1、从 authentication 中获取 用户名
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

		boolean cacheWasUsed = true;
		// 2、根据 username 从缓存中获取认证成功的 UserDetails 信息
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
				// 3、如果缓存中没有用户信息 需要 获取用户信息(由 DaoAuthenticationProvider 实现 )
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			// 4、前置检查账户是否锁定,过期,冻结(由 DefaultPreAuthenticationChecks 类实现)
			preAuthenticationChecks.check(user);
			// 5、主要是验证 获取到的用户密码与传入的用户密码是否一致
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// 缓存出了 bug,再查一次
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		// 6、后置检查用户密码是否 过期
		postAuthenticationChecks.check(user);

		// 7、验证成功后的用户信息存入缓存
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		// 8、重新创建一个 authenticated 为true (即认证成功)的 UsernamePasswordAuthenticationToken 对象并返回
		return createSuccessAuthentication(principalToReturn, authentication, user);
}



// DaoAuthenticationProvider 的 retrieveUser()
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			// 通过 UserDetailsService 的 loadUserByUsername() 方法 获取用户信息
			// 这个方法就是我们自己实现
            // AuthorizationServerSecurityConfiguration 在设置 AuthorizationServerSecurityConfigurer 时,添加的 UserDetailsService 实现是 ClientDetailsUserDetailsService
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
}


// UserDetailsService 就是我们自己需要自定义的内容,封装我们自己的用户、角色。
@Service
public class JwtUserDetailsServer implements UserDetailsService {
    @Resource
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDTO userByUsername = userDao.getUserByUsername(username);
        List<String> permissions = userDao.findPermissionsByUserId(userByUsername.getId());
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        permissions.forEach(p -> authorities.add(new SimpleGrantedAuthority(p)));

        return new User(JSONObject.toJSONString(userByUsername), userByUsername.getPassword(), authorities);
    }
}

2、BasicAuthenticationFilter 实现 Web 的 OncePerRequestFilter,所以不看 doFilter(),直接看BasicAuthenticationFilter 中的 doFilterInternal() 实现

@Override
protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain)
					throws IOException, ServletException {
		final boolean debug = this.logger.isDebugEnabled();
		try {
			// 获取请求中的 token,如果是我们接口调用 /oauth/token,就是从这里取出 token,也就是 username:password
			UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
			if (authRequest == null) {
				// 没有 token 就放行
				chain.doFilter(request, response);
				return;
			}

			String username = authRequest.getName();

			if (debug) {
				this.logger
						.debug("Basic Authentication Authorization header found for user '"
								+ username + "'");
			}

			if (authenticationIsRequired(username)) {
				// 通过 认证管理器进行认证
				// AuthenticationManager 的实现是 ProviderManager
				Authentication authResult = this.authenticationManager.authenticate(authRequest);

				if (debug) {
					this.logger.debug("Authentication success: " + authResult);
				}

				// 设置到上下文
				SecurityContextHolder.getContext().setAuthentication(authResult);

				// 记住我的功能
				this.rememberMeServices.loginSuccess(request, response, authResult);

				onSuccessfulAuthentication(request, response, authResult);
			}

		}
		catch (AuthenticationException failed) {
			SecurityContextHolder.clearContext();

			if (debug) {
				this.logger.debug("Authentication request for failed: " + failed);
			}

			this.rememberMeServices.loginFail(request, response);

			onUnsuccessfulAuthentication(request, response, failed);

			if (this.ignoreFailure) {
				chain.doFilter(request, response);
			}
			else {
				this.authenticationEntryPoint.commence(request, response, failed);
			}

			return;
		}

		chain.doFilter(request, response);
}

BasicAuthenticationFilter 用于客户端,UserDetailsService 实现是 ClientDetailsUserDetailsService,读取的是 oauth 的表 oauth_client_details。

3、ExceptionTranslationFilter,用处就在于它捕获 AuthenticationException 和 AccessDeniedException。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		} catch (IOException ex) {
			throw ex;
		} catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);

			// 获取异常链中的认证异常 AuthenticationException
			RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			// 没有认证异常 AuthenticationException,那么查找访问被拒绝异常 AccessDeniedException
			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}

			// 如果存在认证异常 或者 查找访问被拒绝异常,那么就需要处理,如果是其它类异常,直接抛出去
			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}
				handleSpringSecurityException(request, response, chain, ase);
			} else {
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				} else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw new RuntimeException(ex);
			}
		}
}

private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {

		// 处理认证异常
		if (exception instanceof AuthenticationException) {
			logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);

			// 认证异常;重定向到身份验证入口点
			sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
		}
		// 处理查找访问被拒绝异常
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

			// 匿名用户或者是记住我,那么重定向到身份验证入口点;
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				logger.debug(
						"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
						exception);

				// 认证异常;重定向到身份验证入口点
				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
								messages.getMessage(
										"ExceptionTranslationFilter.insufficientAuthentication",
										"Full authentication is required to access this resource")));
			} else {
				logger.debug(
						"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
						exception);

				// 权限不足
				// 发送 403 错误
				accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
			}
		}
}

4、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) {
				// 给请求设置 FILTER_APPLIED,如果发生重试,就不需要校验权限了
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}

			// FIXME 主要功能就是判断认证成功的用户是否有权限访问接口
			// 如果是没有权限,那么会抛出无权限访问异常,在 ExceptionTranslationFilter 会专门处理该异常:返回登录页面或 403
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				// 调用接口的方法
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null);
		}
}


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

		// 1、获取访问接口的客户端存在的权限信息:read,write

		// 通常该方法和 antMatchers() 和 access() 搭配使用,目的是拦截 url,需要请求的客户端需要存在的权限
		//       // authorizeRequests() 开启 url 拦截权限功能
		//       .authorizeRequests()
		//       // 指定哪些路径需要验证
		//       .antMatchers("/**")
		//       // 指定路径需要的权限(通用,同时满足hasAnyScope、hasAnyRole、hasAnyAuthority)
		//       .access("#oauth2.hasAnyScope('read,write')")
		//       // 指定哪些路径需要验证
        //       .antMatchers("/**")
		//       .hasAnyRole("read","write")
		//       // 指定哪些路径需要验证
		//       .antMatchers("/**")
		//       .hasAnyAuthority("read,write")
		// SecurityMetadataSource:DefaultFilterInvocationSecurityMetadataSource

		// 或者是 Controller 上的权限,需要注解 @EnableGlobalMethodSecurity 开启
		// prePostEnabled = true,就可以开启以下:@PreAuthorize("hasAnyAuthority('ADMIN')")、@PreAuthorize("hasAnyRole('ADMIN')") 或 @PreAuthorize("hasAnyRole('RILE_ADMIN')")
		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);
		}

		// 2、获取当前访问客户端的存在的权限
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
			// 3、默认调用 AffirmativeBased.decide() 方法, 其内部使用 AccessDecisionVoter 对象进行投票机制判权,判权失败直接抛出 AccessDeniedException 异常
			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);
		}
}

了解了以上过滤器,就基本明白了 spring security 的工作原理。

3、资源服务器的示例

spring security oauth2.0 中,标明资源服务器和认证服务器几乎一致,只是把注解 @EnableAuthorizationServer 改为 @EnableResourceServer。

但是使用 @EnableResourceServer 需要注意的是,一定要设置一种认证拦截方式,如:

.authorizeRequests().antMatchers("/**").permitAll(); 

因为在 AbstractConfiguredSecurityBuilder 的 doBuild() 的 configure(); 才会执行 @EnableResourceServer 引入的 ResourceServerConfiguration 在configure() 方法中构建ResourceServerSecurityConfigurer,而这个 Configure 又会尝试增加 ExpressionUrlAuthorizationConfigurer,这个时候的状态已经是 BuildState.CONFIGURING,不允许新增 Configurer 了,所以启动就抛出异常了。

资源服务器不再是继承 WebSecurityConfigurerAdapter,因为 ResourceServerConfiguration 就继承了 WebSecurityConfigurerAdapter。

@Override
protected void configure(HttpSecurity http) throws Exception {
		// 构建 ResourceServerSecurityConfigurer
		ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
		ResourceServerTokenServices services = resolveTokenServices();
		if (services != null) {
			resources.tokenServices(services);
		}
		else {
			if (tokenStore != null) {
				resources.tokenStore(tokenStore);
			}
			else if (endpoints != null) {
				resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
			}
		}
		if (eventPublisher != null) {
			resources.eventPublisher(eventPublisher);
		}
		// FIXME 添加资源的安全配置。
		for (ResourceServerConfigurer configurer : configurers) {
			configurer.configure(resources);
		}
		// @formatter:off
		http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
		// N.B. exceptionHandling is duplicated in resources.configure() so that
		// it works
		.exceptionHandling()
				.accessDeniedHandler(resources.getAccessDeniedHandler()).and()
				.sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
				.csrf().disable();
		// @formatter:on
		// 这里将 ResourceServerSecurityConfigurer 添加到 HttpSecurity
		http.apply(resources);
		if (endpoints != null) {
			// Assume we are in an Authorization Server
			http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
		}
		// FIXME 添加 HTTP 的安全配置。
		for (ResourceServerConfigurer configurer : configurers) {
			// Delegates can add authorizeRequests() here
			configurer.configure(http);
		}
		if (configurers.isEmpty()) {
			// Add anyRequest() last as a fall back. Spring Security would
			// replace an existing anyRequest() matcher with this one, so to
			// avoid that we only add it if the user hasn't configured anything.
			http.authorizeRequests().anyRequest().authenticated();
		}
}

可以看到 ResourceServerConfigurer.configure(resources); 和 ResourceServerConfigurer.configure(http); 这两处都需要重写资源服务器的配置,所以我们继承接口的子类 ResourceServerConfigurerAdapter。

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1")  // 标记,以指示这些资源需要认证,默认 oauth2-resource ,设置为 null ,表示都不需要认证
                .tokenStore(tokenStore)
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().cors().disable()
                // 开启url 拦截权限功能
                .authorizeRequests()
                // 指定哪些路径需要验证
               .antMatchers("/**")
                //.antMatchers("/**")
                .permitAll();
    }
}

继续看源码,上面构建了一个 ResourceServerSecurityConfigurer,实现了 SecurityConfigurer,在 AbstractConfiguredSecurityBuilder 的 doBuild() 的 configure(); 调用,

 @Override
    public void configure(HttpSecurity http) throws Exception {

        // 1、创建 OAuth2AuthenticationManager  对象
        AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);

        // 2、FIXME 创建 OAuth2AuthenticationProcessingFilter 过滤器
        resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
        resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        // 设置认证管理器
        resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
        if (eventPublisher != null) {
            resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
        }
        if (tokenExtractor != null) {
            resourcesServerFilter.setTokenExtractor(tokenExtractor);
        }
        resourcesServerFilter = postProcess(resourcesServerFilter);
        resourcesServerFilter.setStateless(stateless);

        // @formatter:off
        http
                // authorizeRequests() 会尝试添加 ExpressionUrlAuthorizationConfigurer,添加就是抛出异常。
                .authorizeRequests()
                .expressionHandler(expressionHandler)
                .and()
                // 3、 将 OAuth2AuthenticationProcessingFilter 过滤器加载到过滤器链上
                .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
                .authenticationEntryPoint(authenticationEntryPoint);
        // @formatter:on
}

在 AbstractPreAuthenticatedProcessingFilter 前面加了一个包含 AuthenticationManager 的过滤器OAuth2AuthenticationProcessingFilter,其实这里只是使用AbstractPreAuthenticatedProcessingFilter 的排序-1(1200-1),刚好在认证服务器过滤器链的认证过滤器位置(Basic、UsernamePassword),这里用来验证 token。

// OAuth2AuthenticationProcessingFilter 的 doFilter()
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
			ServletException {

		final boolean debug = logger.isDebugEnabled();
		final HttpServletRequest request = (HttpServletRequest) req;
		final HttpServletResponse response = (HttpServletResponse) res;

		try {

			// 1、 调用 tokenExtractor.extract() 方法,从请求中解析出 token 信息并存放到 authentication 的 principal 字段中
			Authentication authentication = tokenExtractor.extract(request);
			
			if (authentication == null) {// 没有认证信息

				// 无状态(stateless = false,表示不拦截该资源,不需要认证)
				// 或上下文已存在认证信息
				if (stateless && isAuthenticated()) {
					if (debug) {
						logger.debug("Clearing security context.");
					}
					SecurityContextHolder.clearContext();
				}
				if (debug) {
					logger.debug("No token in request, will continue chain.");
				}
			}
			else {

				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
				if (authentication instanceof AbstractAuthenticationToken) {
					AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
					// 认证详情
					needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
				}
				// 2、 调用 authenticationManager.authenticate() 进行认证
				// 注意此时的  authenticationManager 是 OAuth2AuthenticationManager
				Authentication authResult = authenticationManager.authenticate(authentication);

				if (debug) {
					logger.debug("Authentication success: " + authResult);
				}

				eventPublisher.publishAuthenticationSuccess(authResult);
				SecurityContextHolder.getContext().setAuthentication(authResult);

			}
		}
		catch (OAuth2Exception failed) {
			SecurityContextHolder.clearContext();

			if (debug) {
				logger.debug("Authentication request failed: " + failed);
			}
			eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
					new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

			authenticationEntryPoint.commence(request, response,
					new InsufficientAuthenticationException(failed.getMessage(), failed));

			return;
		}

		chain.doFilter(request, response);
}

// OAuth2AuthenticationManager 的 authenticate()
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		if (authentication == null) {
			throw new InvalidTokenException("Invalid token (token not found)");
		}
		// 1、从 authentication 中获取 access_token
		String token = (String) authentication.getPrincipal();
		// 2、调用 tokenServices.loadAuthentication() 方法,将 jwt 格式的 access_token 还原回来,这里的 tokenServices 就是我们资源服务器配置的。
		OAuth2Authentication auth = tokenServices.loadAuthentication(token);
		if (auth == null) {
			throw new InvalidTokenException("Invalid token: " + token);
		}

		// 取出数据库中配置的表 oauth_client_details 字段 resource_ids 存储的资源ID
		// 是否包含当前项目配置的 resourceId,默认值是 oauth2-resource
		// FIXME 如果当前项目将 resourceId 设置为 null,就不需要匹配。
		Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
		if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
			throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
		}

		// 3、检测客户端信息
		// 如果资源服务器没有配置 ClientDetailsService 的实现,就不需要校验;
		// 如果要校验,并且 ClientDetailsService 实现是 JDBC ,那么需要连接同一个数据库,但是微服务规范是每一个服务都有独有的数据库,所以不合规范,
		// 如果实现是 REDIS 等,从规范上来看还可以。
		// 假设就是使用 JDBC,开始校验
		checkClientDetails(auth);

		if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
			OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
			// Guard against a cached copy of the same details
			if (!details.equals(auth.getDetails())) {
				// Preserve the authentication details from the one loaded by token services
				details.setDecodedDetails(auth.getDetails());
			}
		}
		// 4、设置认证成功标识并返回
		auth.setDetails(authentication.getDetails());
		auth.setAuthenticated(true);
		return auth;

}

private void checkClientDetails(OAuth2Authentication auth) {
		if (clientDetailsService != null) {
			ClientDetails client;
			try {
				// 取出存储的配置的 客户端信息
				client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());
			}
			catch (ClientRegistrationException e) {
				throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");
			}
			// 对比 scope:可以配置多个scope,同时也可以在请求的时候携带多个,如果请求没携带,那么就不校验
			Set<String> allowed = client.getScope();
			for (String scope : auth.getOAuth2Request().getScope()) {
				if (!allowed.contains(scope)) {
					throw new OAuth2AccessDeniedException(
							"Invalid token contains disallowed scope (" + scope + ") for this client");
				}
			}
		}
}

至此,源码逻辑大概清晰了。

分享一个在线绘制流程图和思维导图的工具,通过链接注册有3天会员奖励。

https://www.processon.com/i/62ea16d876213176f6db97ee?full_name=java-spring-mybatis-redis

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值