欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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是授权访问管理器,默认实现由三种
 

 1UnanimousBased(全票通过):所有投票器都通过才允许访问资源。

2ConsensusBased(少数服从多数):超过一半的投票器通过才允许访问资源。
3AffirmativeBased(一票通过):只要有一个投票器投票通过,就允许访问资源。

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的实现,最简单的有RoleVoterRoleVoter通过将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;
    }