鉴权过滤器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异常,整个鉴权的流程大概就是这样的。
相关推荐
3. **定制Filter**:在Spring Cloud Gateway中,我们可以自定义WebFlux Filter,利用Spring Security提供的API进行认证和鉴权。这通常涉及到`@PreAuthorize`和`@Secured`等注解的使用,以控制对特定路由的访问权限。...
3. **JWT过滤器**:为了实现JWT的验证,你需要创建一个自定义的Filter,如`JwtAuthenticationFilter`。这个过滤器会在每个请求到达控制器之前运行,从中提取JWT并验证其有效性。如果JWT有效,它会创建一个`...
Spring Security 由多个组件构成,包括过滤器链、访问决策管理器、安全上下文持有者等。核心组件是`DelegatingFilterProxy`,它是一个Servlet过滤器,用于代理其他Spring Bean(如`...
综上所述,"过滤器实现鉴权"是一种简化版的权限管理方案,主要适用于学习和简单应用场景。在实际开发中,为了保证系统的安全性和可扩展性,通常会结合数据库和成熟的认证框架进行更复杂的权限控制。
在这个“springboot springsecurity动态权限控制”的主题中,我们将深入探讨如何在Spring Boot项目中集成Spring Security,实现动态权限控制,让菜单权限的管理更加灵活和高效。 首先,我们需要理解Spring Security...
4. **自定义过滤器**:如果标准配置不能满足需求,你可以创建自定义的过滤器并将其插入到Spring Security的过滤链中。这可能包括处理JWT令牌、OAuth2令牌或其他自定义安全逻辑。 5. **异常处理**:Spring Security...
SpringSecurity提供了过滤器链,可以拦截每个HTTP请求,检查令牌的有效性。 为了实现基于角色的访问控制(RBAC),我们需要在SpringSecurity的配置中定义角色和权限映射。这可以通过实现`UserDetailsService`接口...
- **过滤器链**:Spring Security通过一系列过滤器执行安全逻辑,这些过滤器在HTTP请求生命周期中按顺序执行。 在实际项目中,开发者需要根据需求调整`SecurityConfig`,例如添加自定义的认证和授权逻辑,或者配置...
2. `AuthenticationFilter`:Spring Security过滤器,处理Token验证。 3. `TokenBlacklistService`:与Redis交互,管理Token黑名单。 4. `SecurityConfig`:Spring Security的配置类,定制安全规则。 5. `RedisUtils...
它基于Spring Framework 5、Project Reactor和Spring Boot 2构建,支持高并发、低延迟的特性,并且提供了丰富的过滤器链来扩展功能。 2. **项目结构分析** "spring-cloud-gateway-example-master"项目通常包含以下...
在Spring Security中,我们可以创建一个`JwtTokenAuthenticationFilter`,在过滤器链中注册它,使其在每个请求之前执行。这个过滤器会解析JWT,然后使用`JwtTokenAuthenticationProvider`进行认证。如果认证成功,...
主要组件包括过滤器链、安全上下文和授权规则。通过配置Spring Security,我们可以定义哪些用户可以访问哪些资源,以及如何进行身份验证和授权。 二、AngularJS安全实践 在AngularJS应用中,安全主要涉及两个方面:...
Spring Security的过滤器链会拦截这些请求,检查Authorization头中的JWT,并使用相同的密钥进行验证。如果验证成功,过滤器会解析JWT,创建一个代表当前用户的Authentication对象,并将其放入Spring Security的...
在Spring框架中,Acegi(现在已经演变为Spring Security)是一个强大的安全管理组件,它提供了认证、授权等核心安全功能。在Spring_Acegi框架鉴权的实现中,我们主要关注的是如何处理用户的登录验证以及在验证成功或...
SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如:BasicAuthenticationFilter实现的是HttpBasic模式的登录认证,UsernamePasswordAuthenticationFilter实现用户名密码的登录认证,...
在本文中,我们将深入探讨如何使用Spring Boot、Spring Security以及JSON Web Tokens (JWT) 创建一个用户登录系统,并实现角色鉴权。Spring Boot以其简洁的配置和强大的功能在现代Java开发中占据重要地位,而Spring ...
最后来看一下网关服务的安全配置代码片段,这段代码展示了如何在网关服务中启用Spring Security,并自定义安全过滤器链。 ```java @EnableWebFluxSecurity public class GatewaySecurityConfig { @Bean ...
3. **配置Gateway过滤器**:在Gateway中创建自定义过滤器,利用Spring Security提供的认证令牌,进行请求的预处理或后处理。例如,添加一个`JwtAuthenticationFilter`,用于从请求头中提取JWT令牌并进行验证。 4. *...
对于登录鉴权,通常会在 pre 过滤器阶段进行处理,因为这一步发生在请求被路由到具体服务之前。 1. **创建自定义过滤器**:编写一个实现了 `ZuulFilter` 接口的类,并重写 `filterType()`、`filterOrder()`、`...
easy-security 基于过滤器实现的一款配合spring快速开发的安全认证框架,思想是希望通过简单的配置,并且实现核心的方法达到认证和鉴权的目的。easy-security 不限制存取token方式,无论是保存到服务端还是使用JWT等...