spring security 简单分析
程序员文章站
2022-05-25 12:01:23
...
1:spring security 对于普通的URL权限验证基于过滤器实现的
在web.xml 文件中这样配置
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
其中 filter-name一定要配成springSecurityFilterChain,因为DelegatingFilterProxy初始化的时候回根据配置的filter-name 从容器中查找对因的spring security 统一处理过滤器 返回的是FilterChainProxy 。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
//根据请求的URL 放回相应的过滤器
List filters = getFilters(fi.getRequestUrl());
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl() +
filters == null ? " has no matching filters" : " has an empty filter list");
}
chain.doFilter(request, response);
return;
}
//构建spring security 过滤器链对象
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
}
根据Url返回过滤器其中会包含一个FilterSecurityInterceptor过滤器,这个是专门处理http url 权限认证的过滤器,下面是他的doFilter
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 observce
// 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) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//这个方法是权限验证的入口调用父类的beforeInvocation方法
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
在上面我们看到invoke 方法会调用父类的beforeInvocation方法。父类是AbstractSecurityInterceptor,这个类是权限验证的主类,
包含子类有
- EndpointInterceptor
- FilterSecurityInterceptor 对应http资源过滤
- MethodSecurityInterceptor 对应spring aop 拦截过滤
- AspectJSecurityInterceptor
- AspectJAnnotationSecurityInterceptor
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
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());
}
//获取资源对应的权限配置
ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
if (attr == null) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"No public invocations are allowed via this AbstractSecurityInterceptor. "
+ "This indicates a configuration error because the "
+ "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
}
if (logger.isDebugEnabled()) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (logger.isDebugEnabled()) {
logger.debug("Secure object: " + object + "; ConfigAttributes: " + attr);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"), object, attr);
}
//判断每次请求是否都需要进行认证检查
Authentication authenticated = authenticateIfRequired();
try {
//权限验证
this.accessDecisionManager.decide(authenticated, object, attr);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated,
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
if (logger.isDebugEnabled()) {
logger.debug("Authorization successful");
}
AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated);
publishEvent(event);
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
if (runAs == null) {
if (logger.isDebugEnabled()) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(authenticated, false, attr, object);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContextHolder.getContext().setAuthentication(runAs);
// revert to token.Authenticated post-invocation
return new InterceptorStatusToken(authenticated, true, attr, object);
}
}
在上面方法中 this.accessDecisionManager.decide(authenticated, object, attr);才是资源授权的
地方,accessDecisionManager是授权访问管理器,默认实现由三种
(1)UnanimousBased(全票通过):所有投票器都通过才允许访问资源。
(2)ConsensusBased(少数服从多数):超过一半的投票器通过才允许访问资源。
(3)AffirmativeBased(一票通过):只要有一个投票器投票通过,就允许访问资源。
URL资源授权默认走AffirmativeBased这个授权访问管理器,其授权代码如下
public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config)
throws AccessDeniedException {
//获取当前访问授权管理器的投票器
Iterator iter = this.getDecisionVoters().iterator();
int deny = 0;
while (iter.hasNext()) {
AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
int result = voter.vote(authentication, object, config);
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();
}
Spring Security中有很多AccessDecisionVoter的实现,最简单的有RoleVoter。RoleVoter通过将Authentication里面获取的权限信息,与从ConfigAttributeDefinition配置的访问资源需要的权限对比,来投票通过,或拒绝,或弃权。代码如下
public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
int result = ACCESS_ABSTAIN;
Iterator iter = config.getConfigAttributes().iterator();
GrantedAuthority[] authorities = extractAuthorities(authentication);
while (iter.hasNext()) {
ConfigAttribute attribute = (ConfigAttribute) iter.next();
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (int i = 0; i < authorities.length; i++) {
if (attribute.getAttribute().equals(authorities[i].getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}