赞
踩
上一篇我们分析了AuthenticationManagerBuilder的实现,这一篇分析另一个SecurityBuilder实现类——WebSecurity。
1、FilterChainProxy的创建
WebSecurity由WebSecurityConfiguration创建,用于创建称为Spring Security Filter Chain(springSecurityFilterChain)的FilterChainProxy。 springSecurityFilterChain是DelegatingFilterProxy委派给的Filter。
可以通过创建WebSecurityConfigurer或更可能通过重写WebSecurityConfigurerAdapter来对WebSecurity进行自定义。
由上图可知,WebSecurity是来创建一个Web过滤器的,它继承结构与AuthenticationManagerBuilder类似,最终都是使用performBuild()方法完成Filter的创建。
@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) {
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;
}
performBuild()方法实际创建的是一个FilterChainProxy对象,这个对象的构造方法需要传入一个List<SecurityFilterChain>,这个List由两部分:
一部分是集合ignoredRequests中元素RequestMatcher作为构造参数的DefaultSecurityFilterChain
一部分是集合securityFilterChainBuilders中元素SecurityBuilder创建的SecurityFilterChain
关于这两部分的SecurityFilterChain稍后再讲,先看看FilterChainProxy是怎样的一个过滤器。
2、FilterChainProxy介绍
委托将请求过滤到Spring管理的过滤器bean列表。从2.0版开始,您不需要在应用程序上下文中显式配置FilterChainProxy bean,除非您需要对过滤器链内容进行非常精细的控制。大多数情况应该由默认的<security:http />命名空间配置选项充分涵盖。
通过在应用程序web.xml文件中添加标准的Spring DelegatingFilterProxy声明,FilterChainProxy链接到servlet容器过滤器链。
2.1、配置
从版本3.1开始,FilterChainProxy使用SecurityFilterChain实例列表进行配置,每个实例包含一个RequestMatcher和一个应用于匹配请求的过滤器列表。大多数应用程序只包含一个过滤器链,如果您使用的是命名空间,则不必显式设置链。如果需要更精细的控件,则可以使用<filter-chain>命名空间元素。这定义了一个URI模式和过滤器列表(作为逗号分隔的bean名称),它们应该应用于与模式匹配的请求。示例配置可能如下所示:
<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
<constructor-arg>
<util:list>
<security:filter-chain pattern="/do/not/filter*" filters="none"/>
<security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
</util:list>
</constructor-arg>
</bean>
名称“filter1”,“filter2”,“filter3”应该是应用程序上下文中定义的Filter实例的bean名称。名称的顺序定义了过滤器的应用顺序。如上所示,对“过滤器”使用值“无”可以用于完全从安全过滤器链中排除请求模式。有关可用配置选项的完整列表,请参阅security namespace schema文件。
2.2、请求处理
必须输入FilterChainProxy应服务的每种可能模式。给定请求的第一个匹配项将用于定义应用于该请求的所有过滤器。这意味着您必须将大多数特定匹配放在列表顶部,并确保针对相应条目输入应该应用于给定匹配器的所有过滤器。 FilterChainProxy不会遍历其余的映射条目以查找其他过滤器。
FilterChainProxy尊重选择不调用Filter.doFilter(ServletRequest,ServletResponse,FilterChain)的Filter的正常处理,因为不会调用原始或FilterChainProxy声明的过滤器链的其余部分。
请求防火墙
HttpFirewall实例用于验证传入请求并创建包装请求,该请求提供用于匹配的一致路径值。有关默认实现可防御的攻击类型的更多信息,请参阅StrictHttpFirewall。可以注入自定义实现以提供对请求内容的更严格控制,或者如果应用程序需要支持默认拒绝的某些类型的请求。
请注意,这意味着如果需要此保护,则必须将Spring Security过滤器与FilterChainProxy结合使用。不要在web.xml文件中明确定义它们。
FilterChainProxy将使用防火墙实例来获取将在过滤器链中提供的请求和响应对象,因此也可以使用此功能来控制响应的功能。当请求通过安全过滤器链时,将调用reset方法。使用默认实现,这意味着此后将返回servletPath和pathInfo的原始值,而不是用于安全模式匹配的修改后的值。
由于此附加包装功能由FilterChainProxy执行,因此我们不建议您在同一过滤器链中使用多个实例。它不应该仅仅被视为在单个Filter实例中包装过滤器bean的实用程序。
2.3、过滤器生命周期
请注意servlet容器和IoC容器之间的过滤器生命周期不匹配。如DelegatingFilterProxy Javadocs中所述,我们建议您允许IoC容器管理生命周期而不是servlet容器。 FilterChainProxy不会在您添加到应用程序上下文的任何过滤器bean上调用标准过滤器生命周期方法。
2.4、实现
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
//VirtualFilterChain包装的目的是为了filters都会执行
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
//返回与提供的URL匹配的第一个过滤器链。
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
StrictHttpFirewall
严格执行HttpFirewall,拒绝任何带有RequestRejectedException的可疑请求。
以下规则适用于防火墙:
拒绝不允许的HTTP方法。这指定阻止HTTP动词篡改和XST攻击。请参阅setAllowedHttpMethods(Collection)
拒绝未规范化的URL以避免绕过安全约束。没有办法禁用它,因为禁用此约束被认为是非常危险的。允许此行为的一些选项是在防火墙之前规范化请求或使用DefaultHttpFirewall。请记住,规范化请求是脆弱的,为什么请求被拒绝而不是规范化。
拒绝包含不可打印ASCII字符的字符的URL。没有办法禁用它,因为禁用此约束被认为是非常危险的。
拒绝包含分号的URL。请参阅setAllowSemicolon(boolean)
拒绝包含URL编码斜杠的URL。请参阅setAllowUrlEncodedSlash(boolean)
拒绝包含反斜杠的URL。请参阅setAllowBackSlash(boolean)
拒绝包含URL编码百分比的URL。请参阅setAllowUrlEncodedPercent(boolean)
通过doFilterInternal()方法可以看出,真正起到拦截作用的是SecurityFilterChain,下面我们分析上面提到的两种SecurityFilterChain。
3、ignoredRequest为构造参数的DefaultSecurityFilterChain
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
ignoredRequest代表忽略的请求,意思是match()方法返回为true就可以通过拦截,不再受其他过滤器链拦截,因此不需要传入过滤器链。这里有个问题,这个ignoredRequests是怎么产生的呢?
WebSecurity中有个成员变量ignoredRequestRegistry,在ApplicationContextAware接口方法被触发时会赋值三个成员变量:
defaultWebSecurityExpressionHandler
ignoredRequestRegistry
httpFirewall
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.defaultWebSecurityExpressionHandler
.setApplicationContext(applicationContext);
try {
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
PermissionEvaluator.class));
} catch(NoSuchBeanDefinitionException e) {}
this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
try {
this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
} catch(NoSuchBeanDefinitionException e) {}
}
我们可以在外部调用WebSecurity的ignoring()方法返回这个IgnoredRequestConfigurer对象,然后调用其mvcMatchers()方法为WebSecurity的ignoredRequests属性添加RequestMatcher对象。
4、securityFilterChainBuilders创建的SecurityFilterChain
//添加构建器以创建SecurityFilterChain实例。
//通常,此方法在WebSecurityConfigurerAdapter.init(WebSecurity)的框架内自动调用
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
如果开启了@EnableWebSecurity,Spring会在WebSecurityConfiguration中通过setFilterChainProxySecurityConfigurer()方法的参数注入容器中所有的SecurityConfigurer<Filter, WebSecurity>用来配置WebSecurity。具体实现请参考《Spring Security启动过程》。
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
关于WebSecurityConfigurerAdapter的配置细节,请看下面。
5、WebSecurityConfigurerAdapter
5.1、概述
为创建WebSecurityConfigurer实例提供方便的基类。 该实现允许通过重写方法进行自定义。将自动应用从SpringFactoriesLoader查找AbstractHttpConfigurer的结果,以允许开发人员扩展默认值。 为此,您必须创建一个扩展AbstractHttpConfigurer的类,然后在“META-INF/spring.factories”的类路径中创建一个类似于以下内容的类:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
如果您有多个应添加的类,则可以使用“,”来分隔值。 例如:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer,sample.OtherThatExtendsAbstractHttpConfigurer
5.2、继承结构
WebSecurityConfigurerAdapter也是一种SecurityConfigurer,只不过是用来配置构建对象类型是Filter的SecurityBuilder。
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}
5.3、源码分析
我们知道SecurityBuilder在构建目标对象先会先后调用SecurityConfigurer的init()方法和configure()方法对自身进行配置,下面我们就从init()方法开始看起。
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
public void configure(WebSecurity web) throws Exception {
}
HttpSecurity是SecurityBuilder 的实例用来创建SecurityFilterChain,作为WebSecurity创建的FilterChainProxy的过滤器链元素。从上面WebSecurity的performBuild()方法源码看到,在创建完FilterChainProxy对象后,会调用postBuildAction.run()来执行一些完成构建对象后的一些工作,而这里提供的postBuildAction是为WebSecurity提供了一个FilterSecurityInterceptor,关于FilterSecurityInterceptor的用途先有个印象我们稍后再来,这里先探究HttpSecurity的创建过程,然后看看具体可以为WebSecurity提供什么样的SecurityFilterChain。
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
//如果disableLocalConfigureAuthenticationBldr是true则不使用localConfigureAuthenticationBldr而使用authenticationConfiguration的authenticationManager
//默认true
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
//通过使用SecurityContextCallableProcessingInterceptor.beforeConcurrentHandling(NativeWebRequest,Callable)在Callable上填充SecurityContext,
//提供SecurityContext和Spring Web的WebAsyncManager之间的集成。
.addFilter(new WebAsyncManagerIntegrationFilter())
//下面每个方法都是为HttpSecurity配置一个对应功能的SecurityConfigurer
.exceptionHandling().and()//ExceptionHandlingConfigurer
.headers().and()//HeadersConfigurer
.sessionManagement().and()//SessionManagementConfigurer
.securityContext().and()//SecurityContextConfigurer
.requestCache().and()//RequestCacheConfigurer
.anonymous().and()//AnonymousConfigurer
.servletApi().and()//ServletApiConfigurer
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
//允许开发人员扩展默认值。 为此,您必须创建一个扩展AbstractHttpConfigurer的类
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
//重写此方法以配置HttpSecurity。 默认配置是:
//http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
configure(http);
return http;
}
//默认策略是,如果重写configure(AuthenticationManagerBuilder)方法以使用传入的AuthenticationManagerBuilder。
//否则,按类型自动装配AuthenticationManager。
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
上面代码中的objectPostProcessor和authenticationConfiguration都是通过@Autowired方法自动注入的,详情请参考《Spring Security启动过程》。以上就是HttpSecurity的创建过程,下面看HttpSecurity的实现。
6、HttpSecurity
HttpSecurity默认使用AnyRequestMatcher.INSTANCE可以匹配任意请求,但是也提供了一系列方法来对HttpSecurity进行配置。
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
//间接调用apply()方法完成配置
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);
}
这里讲一下用于允许基于HttpServletRequest使用限制访问的authorizeRequests()方法。
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
throws Exception {
ApplicationContext context = getContext();
return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
.getRegistry();
}
这个方法返回一个ExpressionInterceptUrlRegistry,我们可以调用antMatchers()方法注册指定的url,它会将此url封装到一个AuthorizedUrl对象,使用这个对象的方法就可以做到对此url进行指定的访问控制。详细如下调用:
org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry#antMatchers(java.lang.String...)方法:
//映射AntPathRequestMatcher实例列表。
public C antMatchers(HttpMethod method, String... antPatterns) {
return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
}
org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers()方法:
protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.unmappedMatchers = requestMatchers;
return chainRequestMatchersInternal(requestMatchers);
}
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry#chainRequestMatchersInternal()方法:
protected final AuthorizedUrl chainRequestMatchersInternal(
List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
加入要求需要有某个角色的用户才可访问该url,则可以使用hasRole()方法:
//指定URL的快捷方式需要特定的角色。 如果您不想自动插入“ROLE_”,请参阅hasAuthority(String)。
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;
}
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer#hasRole()方法:
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 + "')";
}
不仅hasRole()方法AuthorizedUrl的其它方法也都会调用access()方法,这个方法内部会调用interceptUrl()方法。
//允许将多个RequestMatcher实例注册到ConfigAttribute实例的集合
private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
Collection<ConfigAttribute> configAttributes) {
for (RequestMatcher requestMatcher : requestMatchers) {
REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
requestMatcher, configAttributes));
}
}
在知道authorizeRequests()方法的实现细节后,看看ExpressionUrlAuthorizationConfigurer是如何使用上述配置的。
它的configure()方法定义在父类AbstractInterceptUrlConfigurer中。
public void configure(H http) throws Exception {
//将RequestMatcher与Collection<ConfigAttribute> map封装到ExpressionBasedFilterInvocationSecurityMetadataSource
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) {
return;
}
//最终还是封装成Filter起到拦截作用
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);
}
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;
}
在上面代码中可以看到最终目的是调用HttpSecurity的addFilter()方法加入了一个Filter,注意这个FilterSecurityInterceptor持有了AuthenticationManager对象,AuthenticationManager是在beforeConfigure()方法使用通过构造函数传入的AuthenticationManagerBuilder创建的。如果匹配到需要认证的请求会使用到这个AuthenticationManager。
@Override
protected void beforeConfigure() throws Exception {
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}
既然知道了真正起到拦截作用的是FilterSecurityInterceptor,下面分析一下它的具体实现。
7、FilterSecurityInterceptor
通过过滤器实现执行HTTP资源的安全性处理。
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) {
//过滤器已经应用于此请求,用户希望我们观察每次请求处理一次,因此不要重新进行安全检查
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// 第一次调用此请求时,执行安全检查
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);
}
}
FilterSecurityInterceptor的invoke()方法执行具体拦截行为,具体是beforeInvocation、finallyInvocation、afterInvocation这三个方法,这三个方法定义在父类AbstractSecurityInterceptor中。
AbstractSecurityInterceptor将确保安全拦截器的正确启动配置。它还将实现对安全对象调用的正确处理,即:
从SecurityContextHolder获取Authentication对象。
通过针对SecurityMetadataSource查找安全对象请求,确定请求是否与安全或公共调用相关。
对于受保护的调用(存在用于安全对象调用的ConfigAttributes列表):
如果Authentication.isAuthenticated()返回false,或者alwaysReauthenticate为true,则根据配置的AuthenticationManager对请求进行身份验证。进行身份验证时,将SecurityContextHolder上的Authentication对象替换为返回值。
根据配置的AccessDecisionManager授权请求。
通过配置的RunAsManager执行任何run-as替换。
将控制权传递回具体子类,实际上将继续执行该对象。返回一个InterceptorStatusToken,以便在子类完成对象的执行后,其finally子句可以确保使用finallyInvocation(InterceptorStatusToken)重新调用AbstractSecurityInterceptor并正确整理。
具体的子类将通过afterInvocation(InterceptorStatusToken,Object)方法重新调用AbstractSecurityInterceptor。
如果RunAsManager替换了Authentication对象,则将SecurityContextHolder返回到调用AuthenticationManager之后存在的对象。
如果定义了AfterInvocationManager,则调用调用管理器并允许它替换将返回给调用者的对象。
对于公共调用(安全对象调用没有ConfigAttributes):如上所述,具体的子类将返回一个InterceptorStatusToken,随后在执行安全对象后将其重新呈现给AbstractSecurityInterceptor。当调用afterInvocation(InterceptorStatusToken,Object)时,AbstractSecurityInterceptor不会采取进一步的操作。
控制再次返回具体子类,以及应返回给调用者的Object。然后子类将该结果或异常返回给原始调用者。
7.1、授权检查beforeInvocation()方法
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);
//如果没有需要保护调用的资源,则直接返回null
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);
}
//检查是否已认证,否则调用authenticationManager进行认证
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//使用accessDecisionManager对当前请求授权
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);
//默认使用NullRunAsManager返回null
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);
}
}
//如果Authentication.isAuthenticated()返回false或者属性alwaysReauthenticate已设置为true,
//则检查当前身份验证令牌并将其传递给AuthenticationManager。
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
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
对用户请求的认证是使用的authenticationManager.authenticate(),关于这部分请参考https://blog.csdn.net/shenchaohao12321/article/details/87721655,认证成功后会使用accessDecisionManager.decide()方法完成用户访问的授权,下面分析授权流程。
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
//轮询所有已配置的AccessDecisionVoters,有一个投票通过及代表授权成功
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
这个具体实现只是轮询所有已配置的AccessDecisionVoters,并在任何AccessDecisionVoter肯定投票时授予访问权限。 只有在拒绝投票且没有肯定投票的情况下才能拒绝访问。如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为false)。
那么decisionVoters是什么时候设置到这个AccessDecisionManager对象中去的?
AccessDecisionManager的实例化是在上面讲过的AbstractInterceptUrlConfigurer的createFilterSecurityInterceptor()方法创建FilterSecurityInterceptor时就创建的,这里在回顾一下:
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;
}
所以创建AccessDecisionManager的过程就是在org.springframework.security.config.annotation.web.configurers.AbstractInterceptUrlConfigurer#getAccessDecisionManager方法中:
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);
}
这里实例化的具体AccessDecisionManager是AffirmativeBased,它的构造函数需要一个List<AccessDecisionVoter>,我们先看这个List是如何创建的:
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer#getDecisionVoters():
@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(getExpressionHandler(http));
decisionVoters.add(expressionVoter);
return decisionVoters;
}
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;
}
从上面代码得知,AffirmativeBased中的List<AccessDecisionVoter>是一个WebExpressionVoter,vote()方法如下:
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
关于授权表达式的解析请参考【Spring-Security源码分析】Spring安全表达式解析。
7.2、清理AbstractSecurityInterceptor的工作finallyInvocation()方法
安全对象调用完成后,清理AbstractSecurityInterceptor的工作。 无论安全对象调用成功返回(即应在finally块中完成),都应在安全对象调用之后和afterInvocation之前调用此方法。
protected void finallyInvocation(InterceptorStatusToken token) {
//默认beforeInvocation()方法中返回的InterceptorStatusToken的isContextHolderRefreshRequired为false
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
7.3、检查从安全对象调用返回的Object,能够修改Object或抛出AccessDeniedException。
如果开启了@EnableGlobalMethodSecurity会为FilterSecurityInterceptor注入一个afterInvocationManager。
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
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;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。