Spring Security3源码分析-FilterSecurityInterceptor分析

FilterSecurityInterceptor过滤器对应的类路径为
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
这个filter是filterchain中比较复杂,也是比较核心的过滤器,主要负责授权的工作
在看这个filter源码之前,先来看看spring是如何构造filter这个bean的
具体的构造过程的代码片段为

//这个方法源自HttpConfigurationBuilder类
void createFilterSecurityInterceptor(BeanReference authManager) {
//判断是否配置了use-expressions属性
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
//根据intercept-url标签列表创建授权需要的元数据信息。后面仔细分析
BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);

RootBeanDefinition accessDecisionMgr;
//创建voter列表
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
//如果是使用了表达式,使用WebExpressionVoter
//没使用表达式,就使用RoleVoter、AuthenticatedVoter
if (useExpressions) {
voters.add(new RootBeanDefinition(WebExpressionVoter.class));
} else {
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
}
//定义授权的决策管理类AffirmativeBased
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
//添加依赖的voter列表
accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
accessDecisionMgr.setSource(pc.extractSource(httpElt));

// Set up the access manager reference for http
String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);
//如果未定义access-decision-manager-ref属性,就使用默认的
//AffirmativeBased
if (!StringUtils.hasText(accessManagerId)) {
accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr, accessManagerId));
}
//创建FilterSecurityInterceptor过滤器
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
//添加决策管理器
builder.addPropertyReference("accessDecisionManager", accessManagerId);
//添加认证管理类
builder.addPropertyValue("authenticationManager", authManager);

if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
}
//添加授权需要的安全元数据资源
builder.addPropertyValue("securityMetadataSource", securityMds);
BeanDefinition fsiBean = builder.getBeanDefinition();
//向ioc容器注册bean
String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));

// Create and register a DefaultWebInvocationPrivilegeEvaluator for use with taglibs etc.
BeanDefinition wipe = new RootBeanDefinition(DefaultWebInvocationPrivilegeEvaluator.class);
wipe.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(fsiId));

pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext().generateBeanName(wipe)));

this.fsi = new RuntimeBeanReference(fsiId);
}

现在再仔细分析创建元数据资源的bean过程

static BeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, Element elt, ParserContext pc) {
//创建Url处理类,有两个实现:AntUrlPathMatcher、RegexUrlPathMatcher
UrlMatcher matcher = HttpSecurityBeanDefinitionParser.createUrlMatcher(elt);
boolean useExpressions = isUseExpressions(elt);
//解析intercept-url标签,构造所有需要拦截url的map信息
//map中的key:RequestKey的bean定义,value:SecurityConfig的bean定义
ManagedMap<BeanDefinition, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
interceptUrls, useExpressions, pc);
BeanDefinitionBuilder fidsBuilder;

if (useExpressions) {
//定义表达式处理类的bean
Element expressionHandlerElt = DomUtils.getChildElementByTagName(elt, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");

if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
} else {
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();
expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
}
//定义表达式类型的FilterInvocationSecurityMetadataSource
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
//通过构造函数注入依赖
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
} else {
//定义非表达式类型的FilterInvocationSecurityMetadataSource
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
//通过构造函数注入依赖
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
}

fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));

return fidsBuilder.getBeanDefinition();
}


通过以上的bean构造过程,FilterSecurityInterceptor所依赖的决策管理器、认证管理器、安全元数据资源都具备了,该让FilterSecurityInterceptor干活了,其源码为

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//封装request, response, chain,方便参数传递、增加代码阅读性
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) {
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//执行父类beforeInvocation,类似于aop中的before
InterceptorStatusToken token = super.beforeInvocation(fi);

try {
//filter传递
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
//执行父类的afterInvocation,类似于aop中的after
super.afterInvocation(token, null);
}
}
}

继续看父类的beforeInvocation方法,其中省略了一些不重要的代码片段

protected InterceptorStatusToken beforeInvocation(Object object) {
//根据SecurityMetadataSource获取配置的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//省略……
//判断是否需要对认证实体重新认证,默认为否
Authentication authenticated = authenticateIfRequired();

// Attempt authorization
try {
//决策管理器开始决定是否授权,如果授权失败,直接抛出AccessDeniedException
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));

throw accessDeniedException;
}
}

增加说明

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

这里获取的是权限列表信息,比如说有这个配置
<security:intercept-url pattern="/index.jsp*" access="ROLE_USER,ROLE_ADMIN"/>
如果现在发起一个请求时index.jsp,那么根据这个请求返回的attributes集合就是分别包含ROLE_USER,ROLE_ADMIN属性的两个SecurityConfig对象

至于请求url如何匹配的,大家可以通过阅读DefaultFilterInvocationSecurityMetadataSource类的源码,实际上,这里用到了spring的路径匹配工具类org.springframework.util.AntPathMatcher
AntPathMatcher匹配方式的通配符有三种:
?(匹配任何单字符),*(匹配0或者任意数量的字符),**(匹配0或者更多的目录)

由于之前在bean的定义过程已经知道决策管理器是AffirmativeBased,接着看AffirmativeBased的决策过程

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deny = 0;
//循环voters,实际上是RoleVoter、AuthenticatedVoter
for (AccessDecisionVoter voter : getDecisionVoters()) {
//把具体的决策任务交给voter处理
//voter只返回-1、0、1,只有为1才算授权成功
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;
}
}
//只要有一个voter拒绝了,则直接抛出访问拒绝异常
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
}

// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}

[color=red]实际上,有三种决策管理器,分别为AffirmativeBased、ConsensusBased、UnanimousBased,各自决策的区别是:
AffirmativeBased:只要有一个voter投同意票,就授权成功
ConsensusBased:只要投同意票的大于投反对票的,就授权成功
UnanimousBased:需要一致通过才授权成功[/color]具体决策规则很简单,只是根据voter返回的结果做处理
接下来,分别看RoleVoter、AuthenticatedVoter的源码
RoleVoter:

public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;
//从认证实体中获取所有的权限列表
Collection<GrantedAuthority> authorities = extractAuthorities(authentication);
//循环intercept-url配置的access权限列表
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;

// Attempt to find a matching granted authority
//循环认证实体所拥有的权限列表
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
//只要有相同的权限,直接返回成功1
return ACCESS_GRANTED;
}
}
}
}

return result;
}

AuthenticatedVoter:

public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;

for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;

if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) {
if (isFullyAuthenticated(authentication)) {
return ACCESS_GRANTED;
}
}

if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) {
if (authenticationTrustResolver.isRememberMe(authentication)
|| isFullyAuthenticated(authentication)) {
return ACCESS_GRANTED;
}
}

if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {
if (authenticationTrustResolver.isAnonymous(authentication) || isFullyAuthenticated(authentication)
|| authenticationTrustResolver.isRememberMe(authentication)) {
return ACCESS_GRANTED;
}
}
}
}

return result;
}

[color=red]由于RoleVoter在list列表中的位置处于AuthenticatedVoter前面,只要RoleVoter通过,就不会再执行AuthenticatedVoter了。实际上AuthenticatedVoter只会对IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY三种权限做vote处理。[/color]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值