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 接口认证鉴权入门实践指南

Spring Security-----SpringSocial社交登录详解

spring security oauth2认证中心 集成zuul网关的代码分析

Spring Security----JWT详解

Spring Security实现分布式系统授权