授权过程主要是由FilterSecurityInterceptor拦截器完成的.所以咱们开发分析它的源码,建议大家先看一下上一篇文章<SpringSecurity核心概念>,了解下授权的概念
这是我的SpringSecurity的配置类
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 内存中创建用户信息
*
* @return
*/
@Bean
public UserDetailsService userDetailService() {
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(
User.withUsername("zhangsan")
.password("123")
.authorities("r1").build()
);
userDetailsManager.createUser(
User.withUsername("lisi")
.password("123")
.roles("r2")
.build()
);
userDetailsManager.createUser(
User.withUsername("wangwu")
.password("123")
.authorities("r3")
.build()
);
return userDetailsManager;
}
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService())
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf();
httpSecurity.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("r1")
.antMatchers("/r/**")
.authenticated()
.anyRequest()
.permitAll()
.and()
.formLogin().successForwardUrl("/r/login-success")
.and()
.logout()
.logoutUrl("/logout")
;
httpSecurity.exceptionHandling();
}
}
所以我们先来看一看FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
// ~ Static fields/initializers
// =====================================================================================
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
// ~ Instance fields
// ================================================================================================
/**
* Method that is actually called by the filter chain. Simply delegates to the
* {@link #invoke(FilterInvocation)} method.
*
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
*
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// FilterInvocation 保存与Http Filter有关联的对象
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
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 {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 调用父类方法,调用真正业务逻辑之前要做的事
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 开始处理真正的业务逻辑,比如开始进入handler mapping链 interceptor ... ->controller
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
// 调用父类方法,调用真正业务逻辑之后要做的事
super.afterInvocation(token, null);
}
}
}
由于#beforeInvocation和#afterInvocation都是父类中的逻辑,所以咱们再看下AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private ApplicationEventPublisher eventPublisher;
private AccessDecisionManager accessDecisionManager;
private AfterInvocationManager afterInvocationManager;
private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();
private RunAsManager runAsManager = new NullRunAsManager();
private boolean alwaysReauthenticate = false;
private boolean rejectPublicInvocations = false;
private boolean validateConfigAttributes = true;
private boolean publishAuthorizationSuccess = false;
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
// 判断传入的对象是不是FilterInvocation的对象
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 获取授权信息属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 获取封装好的认证信息
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 交由授权管理器进行决定是否通过
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
}
#beforeInvocation方法的思路是
- 先判断传入的对象是不是
FilterInvocation的对象,如果不是则抛异常(必须保证参数类型的正确性) - 获取授权信息属性集合
- 获取认证信息
- 将
FilterInvocation,Authentication,ConfigAttribute交由认证管理器进行认证
可能大家会好奇ConfigAttribute是什么,那咱们看一下
public interface ConfigAttribute extends Serializable {
String getAttribute();
这个接口只有一个#getAttribute()方法,咱们再看看他的实现类
下面这三个类的名字是不是有点熟悉
PreInvocationExpressionAttribute是对应@PreAuthorize的配置属性PostInvocationExpressionAttribute是对应的@PostAuthorize的配置属性SecurityConfig是对应@Secured的配置属性WebExpressionConfigAttribute是在配置文件中声明的配置属性Jsr250SecurityConfig是jsr250注解的实现,这个在SpringSecurity中不常用
所以在获取到授权属性后,我们看看授权管理器是如何进行决策的, 而Spring通常是面向接口编程的,所以我们看看AccessDecisionManager都由哪些实现类
public interface AccessDecisionManager {
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
我们先看AffirmativeBased,这是AccessDecisionManager的默认实现类
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
// 决策投票器开始投票
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"));
}
// 检查是否允许都弃权,默认允许
checkAllowIfAllAbstainDecisions();
}
}
- 有一个投票器赞同通过的就通过
- 投票器没有赞同通过的,并且有一个拒绝的则抛异常
- 如果都弃权,则通过
既然我们看到逻辑中是通过一系列的投票器来决定是否通过的,所以我们再来看看投票权的逻辑,稍后再看其他两种实现类的逻辑
public interface AccessDecisionVoter<S> {
// ~ Static fields/initializers
// =====================================================================================
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
他有众多实现类,我们挑一个WebExpressionVoter来说下吧
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
// 获取WebExpressionConfigAttribute 属性
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
// 做比对, 比如/r/r1 必须具有 r1权限 与请求路径 /r/r1比对
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
private WebExpressionConfigAttribute findConfigAttribute(
Collection<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
// 这也是一个小细节,只获取第一个符合条件的就会返回,所以再Security配置文件中如果配置多条相似的配置会取第一条
// httpSecurity.authorizeRequests()
// .antMatchers("/r/r1").hasAuthority("r1")
// .antMatchers("/r/**")
// .authenticated()
// 要小的权限放在前面,大的权限放在后面
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute) attribute;
}
}
return null;
}
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setExpressionHandler(
SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
public class ConsensusBased extends AbstractAccessDecisionManager {
// // 允许赞同与拒绝数量可以相等
private boolean allowIfEqualGrantedDeniedDecisions = true;
public ConsensusBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int grant = 0;
int deny = 0;
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:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
// 赞同的数量大于拒绝数量,通过
if (grant > deny) {
return;
}
// 赞同数量小于拒绝数量,拒绝
if (deny > grant) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// 赞同数量与拒绝数量持平,且他们都不为0
if ((grant == deny) && (grant != 0)) {
// 允许赞同与拒绝数量可以相等,通过.默认允许
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
// 不允许,则拒绝
else {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
- 赞同的数量大于拒绝数量,通过
- 赞同数量小于拒绝数量,拒绝
- 赞同数量与拒绝数量持平,且他们都不为0,允许赞同与拒绝数量可以相等,通过.默认允许
- 如果都弃权,则通过
public class UnanimousBased extends AbstractAccessDecisionManager {
public UnanimousBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) throws AccessDeniedException {
int grant = 0;
List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
singleAttributeList.add(null);
// 一条属性一条属性的验证
for (ConfigAttribute attribute : attributes) {
singleAttributeList.set(0, attribute);
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, singleAttributeList);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
default:
break;
}
}
}
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
- 如果存在反对票,则拒绝
- 如果没有反对票,有赞同票,则通过
- 如果都弃权了默认通过
Unanimous还有一个与上面实现类两个不同的一点,他在做授权时,会把授权信息一条一条的传给投票权,让投票器把授权信息都确认一遍,都不反对才会通过; 而上面那俩实现类直接把授权信息的集合传给投票器, 这样如果条配置相似,只取第一个,符个第一个就会投赞同票
最后再通过流程图总结一下
- 首先
FilterSecurityInterceptor从SecurityContextHolder中获取Authentication FilterSecurityInterceptor创建FilterInvocation(包含HttpServletRequest,HttpServletResponse,FilterChain)- 将
FilterInvocation传递给SecurityMetaDataSource获取ConfigAttributes - 将
FilterInvocation,Authentication,ConfigAttributes传递给AccessDecisionManager- 如果授权失败,则抛出
AccessDeniedException,然后ExceptionTranslationFilter处理这个异常 - 如果授权成功,则
FilterSecurityInteceptor继续执行FilterChain后面逻辑
- 如果授权失败,则抛出
最后再提一句,对于在Controller中加了注解@Secured,@PreAuthorize的方法,Spring是通过创建一个MethodSecurityInterceptor拦截器实现权限管理的,它与FilterSecurityInterceptor是兄弟类.
在一个请求进入dispatchservlet时,根据请求路径获取拦截器链,在这个过程中进行权限校验的