spring security认证源码分析之账户权限
Posted 狂奔的骆驼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring security认证源码分析之账户权限相关的知识,希望对你有一定的参考价值。
当我进一步用spring security,首先就有下面三个问题让我很疑惑:
1、spring security到底是在哪个环节验证用户权限的?
2、为什么代码实现层没有直接校验权限的地方?
过滤器
假定写了一个过滤器,继承了OncePerRequestFilter:
public class JwtAuthenticationFilter extends OncePerRequestFilter { private Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private JwtTokenProvider.AuthParameters authParameters; @Autowired private UserService userService; //1.从每个请求header获取token //2.调用前面写的validateToken方法对token进行合法性验证 //3.解析得到username,并从database取出用户相关信息权限 //4.把用户信息以UserDetail形式放进SecurityContext以备整个请求过程使用。 // (例如哪里需要判断用户权限是否足够时可以直接从SecurityContext取出去check @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getJwtFromRequest(request); if(Objects.isNull(token)){ logger.error("Token is null: {}", request.getParameter("username")); } if (jwtTokenProvider.validateToken(token)) { String username = getUsernameFromJwt(token, authParameters.getJwtTokenSecret()); UserDetails userDetails = userService.getUserDetailByUserName(username); Authentication authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext().setAuthentication(authentication); } else { logger.error("no authorization: {}", request.getParameter("username")); } super.doFilter(request, response, filterChain); } /** * Get Bear jwt from request header Authorization. * * @param request servlet request. * @return token or null. */ private String getJwtFromRequest(HttpServletRequest request) { String tokenPrefix = "Bearer "; String headName = "Authorization"; String token = request.getHeader(headName); if (token != null && token.startsWith(tokenPrefix)) { return token.replace(tokenPrefix, ""); } return null; } /** * Get user name from Jwt, the user name have set to jwt when generate token. * * @param token jwt token. * @param signKey jwt sign key, set in properties file. * @return user name. */ private String getUsernameFromJwt(String token, String signKey) { return Jwts.parser().setSigningKey(signKey) .parseClaimsJws(token) .getBody() .getSubject(); } }
这段代码的子类逻辑主要是查询数据库,把对应账户的权限添加到上下文Authentication中。
过滤器的启动顺序时,先执行子类过滤器的逻辑。然后是父类过滤器,之后是其他各种自带过滤器。最后是拦截器。
spring security的权限校验就是拦截器中执行。
拦截器
关键的拦截器AbstractSecurityInterceptor:
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if (!this.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: " + this.getSecureObjectClass());
} else {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
if (debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
Authentication authenticated = this.authenticateIfRequired();
try {
this.accessDecisionManager.decide(authenticated, object, attributes); // 1
} catch (AccessDeniedException var7) {
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
throw var7;
}
if (debug) {
this.logger.debug("Authorization successful");
}
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
if (debug) {
this.logger.debug("RunAsManager did not change Authentication object");
}
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
} else {
if (debug) {
this.logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
} else if (this.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‘");
} else {
if (debug) {
this.logger.debug("Public object - authentication not attempted");
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
接下来看AffirmativeBased的decide的实现:
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; Iterator var5 = this.getDecisionVoters().iterator(); while(var5.hasNext()) { AccessDecisionVoter voter = (AccessDecisionVoter)var5.next(); int result = voter.vote(authentication, object, configAttributes); // 2 这个vote就是在鉴权,返回值只要大于0就认为无权限 if (this.logger.isDebugEnabled()) { this.logger.debug("Voter: " + voter + ", returned: " + result); } switch(result) { case -1: ++deny; break; case 1: return; } } if (deny > 0) { throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } else { this.checkAllowIfAllAbstainDecisions(); } } }
然后进入WebExpressionVoter获得上下文:
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) { assert authentication != null; assert fi != null; assert attributes != null; WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes); if (weca == null) { return 0; } else { EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi); ctx = weca.postProcess(ctx, fi); return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1; // 3 } }
接下来进入
PreInvocationAuthorizationAdviceVoter
public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) { PreInvocationAttribute preAttr = this.findPreInvocationAttribute(attributes); if (preAttr == null) { return 0; } else { boolean allowed = this.preAdvice.before(authentication, method, preAttr); // 4 return allowed ? 1 : -1; } }
ExpressionBasedPreInvocationAdvice:
public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) { PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute)attr; EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); Expression preFilter = preAttr.getFilterExpression(); Expression preAuthorize = preAttr.getAuthorizeExpression(); if (preFilter != null) { Object filterTarget = this.findFilterTarget(preAttr.getFilterTarget(), ctx, mi); this.expressionHandler.filter(filterTarget, preFilter, ctx); } return preAuthorize == null ? true : ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx); // 5 }
EvaluationContext ctx存放了用户的权限Authentication,Authentication保存了authorities数组,其实就是权限。
接下来的代码逻辑是在校验ctx的权限是否存在即authorities数组的是否为空,如果为空表示没有权限,如果size大于0就表示有。
由于接下来这部分代码封装的太抽象,一般人都无法理解了。
ExpressionUtils
public final class ExpressionUtils { public ExpressionUtils() { } public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) { try { return (Boolean)expr.getValue(ctx, Boolean.class); // 6 } catch (EvaluationException var3) { throw new IllegalArgumentException("Failed to evaluate expression ‘" + expr.getExpressionString() + "‘", var3); } } }
SpelExpression
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException { if (this.compiledAst != null) { try { TypedValue contextRoot = context == null ? null : context.getRootObject(); Object result = this.compiledAst.getValue(contextRoot == null ? null : contextRoot.getValue(), context); if (expectedResultType != null) { return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType); } return result; } catch (Throwable var5) { if (this.configuration.getCompilerMode() != SpelCompilerMode.MIXED) { throw new SpelEvaluationException(var5, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION, new Object[0]); } this.interpretedCount = 0; this.compiledAst = null; } } ExpressionState expressionState = new ExpressionState(context, this.configuration); TypedValue typedResultValue = this.ast.getTypedValue(expressionState); this.checkCompile(expressionState); return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType); // 7 }
如上代码的注释部分的标注,到了第7步骤,spring security的权限验证就完成了。
以上是关于spring security认证源码分析之账户权限的主要内容,如果未能解决你的问题,请参考以下文章
Spring实战----Security4.1.3鉴权之美--基于投票的AccessDecisionManager实现及源码分析
Spring Security-----SpringSocial社交登录详解