Spring Security 关键的几个Filter(下)

    上篇文章主要讲了用户的认证,就是UsernamePasswordAuthenticationFilter 这个Filter,这里顺便提一下上篇博文没有提到的AnonymousAuthenticationFilter ,这个Filter其实和UsernamePasswordAuthenticationFilter 基本上一样,只不过UsernamePasswordAuthenticationFilter 需要到数据库认证用户信息,而AnonymousAuthenticationFilter 里面默认创建了一个匿名用户,用户的角色是“ROLE_ANONYMOUS”,不需要再去数据库认证了,这个过滤器默认是不开启的,如果有需要场景,可以开启这个Filter。

下面进入正题,主要讲一下用户的权限FilterSecurityInterceptor 这个Filter。

直接上源码

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        this.invoke(fi);
    }

啥也没干,直接调的invoke(fi)方法,继续看这个方法

public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            if (fi.getRequest() != null && this.observeOncePerRequest) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
            //调用父类的方法去拦截权限
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
            super.afterInvocation(token, (Object)null);
        }

    }

 super.beforeInvocation(fi),这个是父类的方法,这个方法处理权限,看看他们都干了什么

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);
                } 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;
            }
        }
    }

关键方法this.accessDecisionManager.decide(authenticated, object, attributes),这个方法是处理权限的,我们追源码直线先墨迹几句,AccessDecisionManager,这个是决定角色是否有权限访问资源的类,他是个接口,实现它的是个抽象类AbstractAccessDecisionManager,然后通过3个类来实现,

AffirmativeBased 默认的实现,一票通过

ConsensusBased 一票否决

UnanimousBased 半数以上通过

默认的是AffirmativeBased ,直接上AffirmativeBased 的源码

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);
            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();
        }
    }

关键方法int result = voter.vote(authentication, object, configAttributes); 通过返回值判断有没有权限,AccessDecisionVoter 同样是一个接口,vote方法在实现类里,默认的是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;
        }
    }

到这里以后实在是看不懂了,不过索性我们知道判断权限的方法是哪个,我们从判断权限的方法入手,还记得this.accessDecisionManager.decide(authenticated, object, attributes)这个方法吧?是在FilterSecurityInterceptor 这个Filter的父类的beforeInvocation()方法里,那么我们重新实现以下这个方法,写一个实现AccessDecisionManager接口的实现类

package com.springcloud.security;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Iterator;

@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
    // decide 方法是判定是否拥有权限的决策方法,
    //authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
    //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
    //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
    // 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if(null== configAttributes || configAttributes.size() <=0) {
            return;
        }
        ConfigAttribute configAttribute;
        String needRoleId;
        for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
            configAttribute = iter.next();
            needRoleId = configAttribute.getAttribute();//权限
            for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
                String[] roleArr = ga.getAuthority().split(",");
                for(String roleId:roleArr) {
                    if (needRoleId.trim().equals(roleId)) {
                        return;
                    }
                }
            }
        }
        throw new AccessDeniedException("no right");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

我们说说这3个参数是什么,第一个authenticated这个是用户信息里的角色或者资源(这里存的是角色,因为资源太多了),从SecurityContextHolder里得到的,也就是登录的时候放进去的(如果是jwt格式的token登录的话,是每次验证token的时候放到SecurityContextHolder里的),第二个object这个是本次访问的信息(request,response),第三个attributes是本次访问的资源也就是rui地址所对应的角色信息。整个逻辑就是通过第一个参数获取觉得id,然后和第三个参数(访问资源所需要的角色)去比较,有就放行,没有抛异常,第三个参数的获取方法也需要自己实现一下,下面是源码


@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource  {

    @Autowired
    ISysRoleResourceService roleResourceService;
    @Autowired
    ISysResourceService resourceService;

    //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {

        //object 中包含用户请求的request 信息
        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
        String uri = request.getRequestURI();
        Map<String, Object> condition = new HashMap<String, Object>();
        condition.put("url",uri);
        List<SysResource> resourceList = resourceService.selectByMap(condition);//查出资源ID
        if(resourceList != null && resourceList.size() > 0 ) {
            Map<String, Object> condition1 = new HashMap<String, Object>();
            condition1.put("resource_id",resourceList.get(0).getId());
            List<SysRoleResource> roleResourceList = roleResourceService.selectByMap(condition1);
            Collection<ConfigAttribute> array = new ArrayList<>();
            ConfigAttribute configAttribute;
            for (SysRoleResource roleResource : roleResourceList) {
                configAttribute = new SecurityConfig(roleResource.getRoleId().toString());
                array.add(configAttribute);
            }
            return array;
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

最后还需要重新写一下FilterSecurityInterceptor 这个Filter,直接实现它的父接口就行,新建一个MyFilterSecurityInterceptor,代码如下

@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
        //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public void destroy() {

    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }
}

这个和原来的FilterSecurityInterceptor 基本一样,只是设置了我们自己的AccessDecisionManager和FilterInvocationSecurityMetadataSource

最后别忘记在配置文件上添加一条Filter

http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

这里最后还留了一个问题,就是没法把原来的Filter删除掉,就是FilterSecurityInterceptor 还生效,哪位大神能帮忙告诉一下怎么去掉原来的Filter,感激不尽

转载于:https://my.oschina.net/u/3244751/blog/2979145

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值