`
c04s31602
  • 浏览: 45993 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Spring Security3学习-鉴权过滤器

 
阅读更多

鉴权过滤器FilterSecurityInterceptor是11个默认过滤器的最后一个,也是流程很复杂的一个过滤器。它的鉴权不仅仅针对web领域,我们主要讨论对web的鉴权



 这个图是大概的流程,首先有几个相关的对象

FilterSecurityInterceptor:安全过滤器

SecurityMetadataSource:资源元数据

ConfigAttribute:访问该资源的配置信息

AccessDecisionManager:访问决策管理器,可以用<http>元素的access-decision-manager-ref属性来指明一个实现了AccessDecisionManager的Spring Bean。

AuthenticationManager:认证管理器,通过getAuthorities()方法返回权限列表,这里是由ProviderManager实现的。

voter(投票器)是在授权过程中的一个重要角色,它的作用是评估以下的内容: 

  • 要访问受保护资源的请求所对应上下文(如URL请求的IP 地址); 
  • 用户的凭证信息(如果存在的话); 
  • 要试图访问的受保护资源; 
  • 系统的配置以及要访问资源本身的配置参数。

怎么配置的,我们看解析security命名空间时对过滤器的初始化方法createFilterSecurityInterceptor

    void createFilterSecurityInterceptor(BeanReference authManager) {
        //解析use-expressions  
        boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
        //配置资源元数据对象
        BeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
        //访问决策器对象
        RootBeanDefinition accessDecisionMgr;
        //voter对象集合
        ManagedList<BeanDefinition> voters =  new ManagedList<BeanDefinition>(2);

        if (useExpressions) {
            voters.add(new RootBeanDefinition(WebExpressionVoter.class));
        } else {
            voters.add(new RootBeanDefinition(RoleVoter.class));
            voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
        }
        //访问决策对象默认是AffirmativeBased,即如果有任何一个投票器允许访问,请求将被立刻允许,而不管之前可能有的拒绝决定。
        accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
        accessDecisionMgr.getPropertyValues().addPropertyValue("decisionVoters", voters);
        accessDecisionMgr.setSource(pc.extractSource(httpElt));

        // Set up the access manager reference for http
        // 获取配置文件中决策控制器bean的id
        String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);

        //如果配置文件没有指定访问控制器,定义默认的
        if (!StringUtils.hasText(accessManagerId)) {
            accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
            pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr, accessManagerId));
        }
        //设置过滤器对象
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
        
        builder.addPropertyReference("accessDecisionManager", accessManagerId);
        //设置authenticationManager认证对象
        builder.addPropertyValue("authenticationManager", authManager);
        //设置observeOncePerRequest属性,是否每个请求只鉴权一次
        if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
            builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
        }

        builder.addPropertyValue("securityMetadataSource", securityMds);
        BeanDefinition fsiBean = builder.getBeanDefinition();
        String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
        pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));

        // Create and register a DefaultWebInvocationPrivilegeEvaluator for use with taglibs etc.
        BeanDefinition wipe = new RootBeanDefinition(DefaultWebInvocationPrivilegeEvaluator.class);
        wipe.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(fsiId));

        pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext().generateBeanName(wipe)));

        this.fsi = new RuntimeBeanReference(fsiId);
    }

 下面再看默认的鉴权流程

FilterSecurityInterceptor继承了AbstractSecurityInterceptor,看他的doFilter方法

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

 invoke方法

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

            // 鉴权发生在这里
            InterceptorStatusToken token = super.beforeInvocation(fi);

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

 看看AbstractSecurityInterceptor的beforeInvocation流程

    protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        final boolean debug = logger.isDebugEnabled();

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

        // 获取此次访问的资源元数据,默认是从ExpressionBasedFilterInvocationSecurityMetadataSource,根据url和http method获取的
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

        if (attributes == null) {
            if (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'");
            }

            if (debug) {
                logger.debug("Public object - authentication not attempted");
            }

            publishEvent(new PublicInvocationEvent(object));

            return null; // no further work post-invocation
        }

        if (debug) {
            logger.debug("Secure object: " + object + "; Attributes: " + attributes);
        }

        // authentication对象也不能为空
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"), object, attributes);
        }

        // 校验authentication对象,如果isAuthenticated是flase则再次校验,不通过会抛出AuthenticationException
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            //访问决策管理器的decide方法
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            // 如果鉴权失败抛出AccessDeniedException
            throw accessDeniedException;
        }

        if (debug) {
            logger.debug("Authorization successful");
        }

        publishEvent(new AuthorizedEvent(object, attributes, authenticated));

        // Attempt to run as a different user
        // 将此资源元数据中以RUN_AS开头的角色赋予此用户
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);

        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }

            // no further work post-invocation
            return new InterceptorStatusToken(authenticated, false, attributes, object);
        } else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContextHolder.getContext().setAuthentication(runAs);

            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(authenticated, true, attributes, object);
        }
    }

 再看访问控制管理器的decide方法,默认的控制器是AffirmativeBased,只要有一个投票器通过就通过。

    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
        // 被禁止数
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            // 只要有一个投票器通过,即返回
            case AccessDecisionVoter.ACCESS_GRANTED:
                return;
            // 不通过数+1
            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();
    }

 在鉴权不通过时都会抛出AccessDeniedException异常,整个鉴权的流程大概就是这样的。

  • 大小: 94.6 KB
分享到:
评论

相关推荐

    Spring Cloud Gateway 整合 Spring Security 统一登录认证鉴权

    3. **定制Filter**:在Spring Cloud Gateway中,我们可以自定义WebFlux Filter,利用Spring Security提供的API进行认证和鉴权。这通常涉及到`@PreAuthorize`和`@Secured`等注解的使用,以控制对特定路由的访问权限。...

    Spring security+jwt服务鉴权完整代码.zip

    3. **JWT过滤器**:为了实现JWT的验证,你需要创建一个自定义的Filter,如`JwtAuthenticationFilter`。这个过滤器会在每个请求到达控制器之前运行,从中提取JWT并验证其有效性。如果JWT有效,它会创建一个`...

    springsecurity源码(鉴权有缺陷)

    Spring Security 由多个组件构成,包括过滤器链、访问决策管理器、安全上下文持有者等。核心组件是`DelegatingFilterProxy`,它是一个Servlet过滤器,用于代理其他Spring Bean(如`...

    过滤器实现鉴权

    综上所述,"过滤器实现鉴权"是一种简化版的权限管理方案,主要适用于学习和简单应用场景。在实际开发中,为了保证系统的安全性和可扩展性,通常会结合数据库和成熟的认证框架进行更复杂的权限控制。

    springboot springsecurity动态权限控制

    在这个“springboot springsecurity动态权限控制”的主题中,我们将深入探讨如何在Spring Boot项目中集成Spring Security,实现动态权限控制,让菜单权限的管理更加灵活和高效。 首先,我们需要理解Spring Security...

    spring security http接口鉴权使用示范项目

    4. **自定义过滤器**:如果标准配置不能满足需求,你可以创建自定义的过滤器并将其插入到Spring Security的过滤链中。这可能包括处理JWT令牌、OAuth2令牌或其他自定义安全逻辑。 5. **异常处理**:Spring Security...

    SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现

    SpringSecurity提供了过滤器链,可以拦截每个HTTP请求,检查令牌的有效性。 为了实现基于角色的访问控制(RBAC),我们需要在SpringSecurity的配置中定义角色和权限映射。这可以通过实现`UserDetailsService`接口...

    springboot-springsecurity-demo

    - **过滤器链**:Spring Security通过一系列过滤器执行安全逻辑,这些过滤器在HTTP请求生命周期中按顺序执行。 在实际项目中,开发者需要根据需求调整`SecurityConfig`,例如添加自定义的认证和授权逻辑,或者配置...

    springboot+jwt+spring-security.rar

    2. `AuthenticationFilter`:Spring Security过滤器,处理Token验证。 3. `TokenBlacklistService`:与Redis交互,管理Token黑名单。 4. `SecurityConfig`:Spring Security的配置类,定制安全规则。 5. `RedisUtils...

    spring-cloud-gateway-example-master.zip

    它基于Spring Framework 5、Project Reactor和Spring Boot 2构建,支持高并发、低延迟的特性,并且提供了丰富的过滤器链来扩展功能。 2. **项目结构分析** "spring-cloud-gateway-example-master"项目通常包含以下...

    springboot 整合security jwt 身份鉴权

    在Spring Security中,我们可以创建一个`JwtTokenAuthenticationFilter`,在过滤器链中注册它,使其在每个请求之前执行。这个过滤器会解析JWT,然后使用`JwtTokenAuthenticationProvider`进行认证。如果认证成功,...

    tut-spring-security-and-angular-js:Spring Security和Angular ::关于如何在具有各种后端体系结构的单页应用程序中使用Spring Security的教程,范围从简单的单个服务器到具有OAuth2身份验证的API网关

    主要组件包括过滤器链、安全上下文和授权规则。通过配置Spring Security,我们可以定义哪些用户可以访问哪些资源,以及如何进行身份验证和授权。 二、AngularJS安全实践 在AngularJS应用中,安全主要涉及两个方面:...

    SpringSecurity之JWT实现token认证和授权.zip

    Spring Security的过滤器链会拦截这些请求,检查Authorization头中的JWT,并使用相同的密钥进行验证。如果验证成功,过滤器会解析JWT,创建一个代表当前用户的Authentication对象,并将其放入Spring Security的...

    Spring源代码解析(九):Spring_Acegi框架鉴权的实现.doc

    在Spring框架中,Acegi(现在已经演变为Spring Security)是一个强大的安全管理组件,它提供了认证、授权等核心安全功能。在Spring_Acegi框架鉴权的实现中,我们主要关注的是如何处理用户的登录验证以及在验证成功或...

    浅析Spring Security登录验证流程

    SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如:BasicAuthenticationFilter实现的是HttpBasic模式的登录认证,UsernamePasswordAuthenticationFilter实现用户名密码的登录认证,...

    spring boot + security + jwt 制作用户登录,角色鉴权

    在本文中,我们将深入探讨如何使用Spring Boot、Spring Security以及JSON Web Tokens (JWT) 创建一个用户登录系统,并实现角色鉴权。Spring Boot以其简洁的配置和强大的功能在现代Java开发中占据重要地位,而Spring ...

    springcloud微服务里的oauth2集成总结.docx

    最后来看一下网关服务的安全配置代码片段,这段代码展示了如何在网关服务中启用Spring Security,并自定义安全过滤器链。 ```java @EnableWebFluxSecurity public class GatewaySecurityConfig { @Bean ...

    微服务网关gateway集成security

    3. **配置Gateway过滤器**:在Gateway中创建自定义过滤器,利用Spring Security提供的认证令牌,进行请求的预处理或后处理。例如,添加一个`JwtAuthenticationFilter`,用于从请求头中提取JWT令牌并进行验证。 4. *...

    zuul网关登陆鉴权/动态路由

    对于登录鉴权,通常会在 pre 过滤器阶段进行处理,因为这一步发生在请求被路由到具体服务之前。 1. **创建自定义过滤器**:编写一个实现了 `ZuulFilter` 接口的类,并重写 `filterType()`、`filterOrder()`、`...

    基于过滤器实现的一款配合spring快速开发的安全认证框架,思想是希望通过简单的配置,并且实现核心的方法达到认证和鉴权的目的

    easy-security 基于过滤器实现的一款配合spring快速开发的安全认证框架,思想是希望通过简单的配置,并且实现核心的方法达到认证和鉴权的目的。easy-security 不限制存取token方式,无论是保存到服务端还是使用JWT等...

Global site tag (gtag.js) - Google Analytics