Spring-security源码-注解权限原理之MethodSecurityInterceptor(二十一)
1.Spring-Security系列导航2.spring-security使用-登录(一)3.spring-security使用-自定义数据源(二)4.spring-security使用-更友好的方式扩展登录AuthenticationProvider(三)5.spring-security使用-获得当前用户信息(四)6.spring-security使用-同一个账号只允许登录一次(五)7.spring-security使用-session共享(六)8.spring-security使用-安全防护HttpFirewall(七)9.spring-security使用-权限控制(八)10.spring-security源码-初始化(九)11.spring-security源码-如何初始化SecurityFilterChain到Servlet12.spring-security源码-FilterChainProxy13.spring-security源码-Filter之WebAsyncManagerIntegrationFilter(十)14.Spring-security源码-Filter之SecurityContextPersistenceFilter(十一)15.Spring-security源码-Filter之HeaderWriterFilter(十二)16.Spring-security源码-Filter之LogoutFilter(十三)17.Spring-security源码-Filter之UsernamePasswordAuthenticationFilter(十四)18.Spring-security源码-Filter之ConcurrentSessionFilter(十五)19.Spring-security源码-Filter之SessionManagementFilter(十六)20.Spring-security源码-Filter之RememberMeAuthenticationFilter(十七)21.Spring-security源码-Filter之ExceptionTranslationFilter(十八)22.Spring-security源码-Filter之FilterSecurityInterceptor(十九)23.Spring-security源码-注解权限原理(二十)
24.Spring-security源码-注解权限原理之MethodSecurityInterceptor(二十一)
25.Spring-Security基于源码扩展-一套系统多套登录逻辑(二十二)26.Spring-Security基于源码扩展-自定义登录(二十三)27.Spring-Security基于源码扩展-自定义认证失败返回(二十四)28.Spring-Security基于源码扩展-自定义授权注解(二十五)MethodSecurityInterceptor是Spring Security对于Spring Aop的切入逻辑
@Override public Object invoke(MethodInvocation mi) throws Throwable { //<1>调用方法前的权限校验 InterceptorStatusToken token = super.beforeInvocation(mi); Object result; try { result = mi.proceed(); } finally { //允许重置ServletContext super.finallyInvocation(token); } //<5>调用方法后的权限校验 return super.afterInvocation(token, result); }
<1>
org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation
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()); } //<2>基于object获取对应的ConfigAttribute 也就是我们的权限配置 初始化处参考 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); //如果配有配置表示不需要权限校验 if (CollectionUtils.isEmpty(attributes)) { Assert.isTrue(!this.rejectPublicInvocations, () -> "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 (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized public object %s", object)); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } //如果未登录 抛出异常 if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } //如果登录过期触发自动authenticationManager.authenticate 重新认证 Authentication authenticated = authenticateIfRequired(); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes)); } // <3>现在有方法 和我们的权限配置则校验授权 attemptAuthorization(object, attributes, authenticated); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes)); } if (this.publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs != null) { SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs)); } // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null"); // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); }
<2>
org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource#getAttributes
@Override public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) { //方法和class作为cacheKey DelegatingMethodSecurityMetadataSource.DefaultCacheKey cacheKey = new DelegatingMethodSecurityMetadataSource.DefaultCacheKey(method, targetClass); //同步锁 synchronized (this.attributeCache) { //先判断cache是否有 Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey); // 如果有直接返回 if (cached != null) { return cached; } //遍历MethodSecurityMetadataSource 解析注解获得ConfigAttribute Collection<ConfigAttribute> attributes = null; for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { attributes = s.getAttributes(method, targetClass); if (attributes != null && !attributes.isEmpty()) { break; } } // Put it in the cache. if (attributes == null || attributes.isEmpty()) { this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); return NULL_CONFIG_ATTRIBUTE; } this.attributeCache.put(cacheKey, attributes); return attributes; } }
<3>
org.springframework.security.access.intercept.AbstractSecurityInterceptor#attemptAuthorization
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) { try { //<4>交给accessDecisionManager处理 初始化处参考 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException ex) {//针对授权不过的抛出异常 if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager)); } else if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); } publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex)); throw ex; } }
<4>
@Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; //遍历voter 调用vote方法进行处理 for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { //授权通过直接返回 case AccessDecisionVoter.ACCESS_GRANTED: return; //针对授权不过 返回ACCESS_DENIED case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
<5>
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) { if (token == null) { // public object return returnedObject; } finallyInvocation(token); // continue to clean in this method for passivity if (this.afterInvocationManager != null) { // Attempt after invocation handling try { returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject); } catch (AccessDeniedException ex) { publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), ex)); throw ex; } } return returnedObject; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2016-11-15 数据库事物导致的脏读 不可重复读 幻读的处理方法