zoukankan      html  css  js  c++  java
  • 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的权限验证就完成了。

     
  • 相关阅读:
    MySQL 基础 查询
    Mysql+keepalived双主
    Kubernetes(二)K8S基础架构
    Kubernetes(一)K8S入门
    Docker (五) 利用Dockerfile创建Nginx镜像
    Docker (四) 使用Dockerfile的方式构建镜像
    Docker (三) 使用commit创建Docker镜像
    Docker (二) Docker网络配置
    Ansible (四) 角色Roles
    Docker (一) Docker入门
  • 原文地址:https://www.cnblogs.com/geektcp/p/12287339.html
Copyright © 2011-2022 走看看