SpringSecurity授權(quán)機(jī)制的實現(xiàn)(AccessDecisionManager與投票決策)
引言
在企業(yè)級應(yīng)用開發(fā)中,安全控制不僅包括認(rèn)證(Authentication)——確認(rèn)用戶身份,還包括授權(quán)(Authorization)——確定用戶是否有權(quán)執(zhí)行特定操作。Spring Security提供了一套精心設(shè)計的授權(quán)機(jī)制,其核心是AccessDecisionManager和投票系統(tǒng)。與簡單的角色檢查相比,這種機(jī)制提供了更細(xì)粒度、更靈活的訪問控制能力。本文將深入探討Spring Security授權(quán)框架的內(nèi)部工作原理,重點分析AccessDecisionManager如何通過投票機(jī)制做出授權(quán)決策,以及如何根據(jù)業(yè)務(wù)需求進(jìn)行定制化配置。通過這些知識,開發(fā)者可以構(gòu)建既安全又靈活的訪問控制系統(tǒng)。
一、Spring Security授權(quán)架構(gòu)
Spring Security的授權(quán)架構(gòu)采用了責(zé)任鏈和投票模式相結(jié)合的設(shè)計,使授權(quán)決策過程模塊化且可擴(kuò)展。授權(quán)過程始于SecurityFilterChain中的FilterSecurityInterceptor,它攔截受保護(hù)資源的請求,收集安全元數(shù)據(jù)(如所需權(quán)限),然后委托給AccessDecisionManager進(jìn)行授權(quán)判斷。AccessDecisionManager通過組合多個AccessDecisionVoter實現(xiàn)復(fù)雜的授權(quán)策略,每個投票者根據(jù)自己的邏輯對授權(quán)請求投贊成、反對或棄權(quán)票。這種分層設(shè)計使得授權(quán)邏輯與業(yè)務(wù)代碼完全分離,便于維護(hù)和擴(kuò)展。
// FilterSecurityInterceptor 關(guān)鍵部分 public class FilterSecurityInterceptor extends AbstractSecurityInterceptor { private final FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 封裝HTTP請求 FilterInvocation fi = new FilterInvocation(request, response, chain); // 進(jìn)行安全攔截 invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { // 檢查安全攔截器是否應(yīng)該被應(yīng)用 if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)) { // 安全攔截器已應(yīng)用,繼續(xù)執(zhí)行過濾器鏈 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); return; } // 標(biāo)記攔截器已應(yīng)用 fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); // 執(zhí)行安全攔截 InterceptorStatusToken token = super.beforeInvocation(fi); try { // 繼續(xù)執(zhí)行過濾器鏈 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { // 清理安全上下文 super.finallyInvocation(token); } // 執(zhí)行后處理 super.afterInvocation(token, null); } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } }
二、AccessDecisionManager接口設(shè)計
AccessDecisionManager是Spring Security授權(quán)體系的核心接口,負(fù)責(zé)協(xié)調(diào)多個AccessDecisionVoter并做出最終授權(quán)決策。它定義了三個關(guān)鍵方法:decide()執(zhí)行授權(quán)判斷,supports(ConfigAttribute)檢查是否支持特定配置屬性,supports(Class)檢查是否支持特定安全對象類型。通過這些方法,AccessDecisionManager可以靈活處理不同類型的授權(quán)請求,如Web請求、方法調(diào)用或特定領(lǐng)域?qū)ο蟮脑L問。Spring Security提供了三種內(nèi)置實現(xiàn):AffirmativeBased(只要有一票贊成即通過)、ConsensusBased(多數(shù)票決定)和UnanimousBased(要求全票通過)。
// AccessDecisionManager接口定義 public interface AccessDecisionManager { /** * 對給定的安全對象做出訪問控制決策 * @param authentication 當(dāng)前用戶的認(rèn)證信息 * @param object 要訪問的安全對象 * @param configAttributes 安全對象的安全配置屬性 * @throws AccessDeniedException 如果拒絕訪問 * @throws InsufficientAuthenticationException 如果認(rèn)證不足 */ void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException; /** * 檢查此AccessDecisionManager是否支持指定的ConfigAttribute * @param attribute 要檢查的配置屬性 * @return 如果支持該屬性返回true */ boolean supports(ConfigAttribute attribute); /** * 檢查此AccessDecisionManager是否支持指定的安全對象類型 * @param clazz 安全對象的類型 * @return 如果支持該類型返回true */ boolean supports(Class<?> clazz); }
三、投票決策實現(xiàn)
Spring Security提供了三種不同的AccessDecisionManager實現(xiàn),每種實現(xiàn)代表不同的投票策略。AffirmativeBased采用"一票通過"策略,只要有一個投票者投贊成票就允許訪問,這是最寬松的策略。ConsensusBased基于"多數(shù)票"原則,根據(jù)贊成票與反對票的比較結(jié)果做出決策。UnanimousBased要求所有投票者都投贊成票(或棄權(quán))才允許訪問,是最嚴(yán)格的策略。這三種實現(xiàn)覆蓋了從寬松到嚴(yán)格的不同安全需求,開發(fā)者可以根據(jù)業(yè)務(wù)場景選擇合適的策略。
// AffirmativeBased實現(xiàn)關(guān)鍵代碼 public class AffirmativeBased extends AbstractAccessDecisionManager { public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) { super(decisionVoters); } @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) throws AccessDeniedException { int deny = 0; // 遍歷所有投票者 for (AccessDecisionVoter voter : getDecisionVoters()) { // 獲取投票結(jié)果 int result = voter.vote(authentication, object, attributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: // 投票通過 return; // 立即返回允許訪問 case AccessDecisionVoter.ACCESS_DENIED: // 投票拒絕 deny++; // 計數(shù)拒絕票 break; default: // 棄權(quán) break; } } // 如果有拒絕票且沒有通過票,則拒絕訪問 if (deny > 0) { throw new AccessDeniedException("訪問被拒絕,安全投票失敗"); } // 所有投票者棄權(quán),根據(jù)配置決定(默認(rèn)允許) if (getAllowIfAllAbstainDecisions()) { return; } // 默認(rèn)拒絕訪問 throw new AccessDeniedException("訪問被拒絕,沒有投票者同意"); } }
四、AccessDecisionVoter機(jī)制
AccessDecisionVoter是Spring Security授權(quán)機(jī)制中的投票者角色,負(fù)責(zé)對特定授權(quán)請求投票。每個投票者實現(xiàn)vote()方法,返回ACCESS_GRANTED(贊成)、ACCESS_DENIED(反對)或ACCESS_ABSTAIN(棄權(quán))。常用的投票者包括:RoleVoter(基于角色投票)、AuthenticatedVoter(基于認(rèn)證狀態(tài)投票)和WebExpressionVoter(基于SpEL表達(dá)式投票)。投票者的靈活性在于它可以基于任何條件做出決策,不僅限于用戶角色,還可以考慮時間、位置、資源屬性等因素。
// AccessDecisionVoter接口 public interface AccessDecisionVoter<S> { /** * 贊成訪問的常量 */ int ACCESS_GRANTED = 1; /** * 拒絕訪問的常量 */ int ACCESS_DENIED = -1; /** * 棄權(quán)的常量 */ int ACCESS_ABSTAIN = 0; /** * 對訪問請求進(jìn)行投票 * @param authentication 當(dāng)前用戶的認(rèn)證信息 * @param object 要訪問的安全對象 * @param attributes 安全對象的配置屬性 * @return 投票結(jié)果:ACCESS_GRANTED、ACCESS_DENIED或ACCESS_ABSTAIN */ int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes); /** * 檢查此投票者是否支持指定的配置屬性 * @param attribute 要檢查的配置屬性 * @return 如果支持該屬性返回true */ boolean supports(ConfigAttribute attribute); /** * 檢查此投票者是否支持指定的安全對象類型 * @param clazz 安全對象的類型 * @return 如果支持該類型返回true */ boolean supports(Class<?> clazz); } // RoleVoter實現(xiàn) public class RoleVoter implements AccessDecisionVoter<Object> { private String rolePrefix = "ROLE_"; public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { // 如果沒有屬性,棄權(quán) if (attributes.isEmpty()) { return ACCESS_ABSTAIN; } // 獲取用戶權(quán)限 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); // 檢查每個配置屬性 for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { // 屬性值作為需要的角色 String role = attribute.getAttribute(); // 檢查用戶是否擁有該角色 for (GrantedAuthority authority : authorities) { if (role.equals(authority.getAuthority())) { return ACCESS_GRANTED; // 用戶有所需角色,允許訪問 } } // 走到這里說明用戶沒有所需角色,返回拒絕 return ACCESS_DENIED; } } // 沒有可支持的屬性,棄權(quán) return ACCESS_ABSTAIN; } @Override public boolean supports(ConfigAttribute attribute) { // 檢查屬性是否以角色前綴開頭 return (attribute.getAttribute() != null) && attribute.getAttribute().startsWith(rolePrefix); } @Override public boolean supports(Class<?> clazz) { return true; // 支持所有類型的安全對象 } }
五、自定義訪問控制規(guī)則
Spring Security的授權(quán)機(jī)制最大的優(yōu)勢在于其可擴(kuò)展性,開發(fā)者可以輕松實現(xiàn)自定義的訪問控制規(guī)則。通過創(chuàng)建自定義的AccessDecisionVoter,可以基于業(yè)務(wù)特定邏輯進(jìn)行授權(quán)決策,如限制特定時間段的訪問、根據(jù)用戶屬性控制權(quán)限或?qū)崿F(xiàn)數(shù)據(jù)行級安全。自定義投票者只需實現(xiàn)AccessDecisionVoter接口的三個方法,然后將其添加到AccessDecisionManager的投票者列表中即可。這種方式使復(fù)雜的授權(quán)需求變得易于實現(xiàn)且可維護(hù)。
// 自定義的工作時間投票者,只允許在工作時間訪問 public class BusinessHoursVoter implements AccessDecisionVoter<Object> { private final int startHour = 9; // 工作開始時間 private final int endHour = 17; // 工作結(jié)束時間 @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { // 檢查是否有工作時間限制的屬性 boolean businessHoursRequired = attributes.stream() .anyMatch(a -> "BUSINESS_HOURS_ONLY".equals(a.getAttribute())); // 如果沒有時間限制,棄權(quán) if (!businessHoursRequired) { return ACCESS_ABSTAIN; } // 獲取當(dāng)前時間 LocalTime now = LocalTime.now(); int currentHour = now.getHour(); // 檢查是否在工作時間內(nèi) if (currentHour >= startHour && currentHour < endHour) { return ACCESS_GRANTED; } else { return ACCESS_DENIED; } } @Override public boolean supports(ConfigAttribute attribute) { return attribute != null && "BUSINESS_HOURS_ONLY".equals(attribute.getAttribute()); } @Override public boolean supports(Class<?> clazz) { return true; } } // 自定義配置 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .antMatchers("/admin/**").access("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login").permitAll(); // 替換默認(rèn)的AccessDecisionManager http.authorizeRequests() .accessDecisionManager(accessDecisionManager()); } @Bean public AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<?>> voters = new ArrayList<>(); voters.add(new WebExpressionVoter()); voters.add(new RoleVoter()); voters.add(new AuthenticatedVoter()); voters.add(new BusinessHoursVoter()); // 添加自定義投票者 // 使用"一票通過"策略 return new AffirmativeBased(voters); } }
六、方法級安全控制
除了Web請求的授權(quán)控制外,Spring Security還提供了方法級別的安全控制,使開發(fā)者能夠直接在業(yè)務(wù)方法上應(yīng)用授權(quán)規(guī)則。通過@PreAuthorize、@PostAuthorize等注解,可以使用SpEL表達(dá)式定義復(fù)雜的訪問條件。這些注解由MethodSecurityInterceptor處理,它同樣使用AccessDecisionManager進(jìn)行授權(quán)決策。方法級安全與Web安全共享相同的授權(quán)架構(gòu),但提供了更精細(xì)的控制粒度,特別適合業(yè)務(wù)邏輯層的權(quán)限管理。
// 啟用方法級安全 @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected AccessDecisionManager accessDecisionManager() { // 自定義方法級安全的AccessDecisionManager AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager(); // 獲取現(xiàn)有的投票者并添加自定義投票者 List<AccessDecisionVoter<?>> voters = new ArrayList<>( accessDecisionManager.getDecisionVoters()); voters.add(new BusinessHoursVoter()); // 創(chuàng)建新的AccessDecisionManager return new AffirmativeBased(voters); } } // 在服務(wù)類中使用方法級安全 @Service public class UserService { @PreAuthorize("hasRole('ADMIN') and hasAuthority('BUSINESS_HOURS_ONLY')") public void deleteUser(Long userId) { // 刪除用戶的邏輯 } @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") public UserDetails viewUserProfile(Long userId) { // 查看用戶資料的邏輯 return userProfile; } @PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')") public UserDetails loadUserById(Long userId) { // 只允許查看自己的詳細(xì)信息或者管理員查看 return userDetails; } }
總結(jié)
Spring Security的授權(quán)機(jī)制以AccessDecisionManager和投票決策系統(tǒng)為核心,提供了一套靈活而強(qiáng)大的訪問控制框架。通過責(zé)任鏈模式和策略模式的結(jié)合,它實現(xiàn)了授權(quán)邏輯的模塊化和可擴(kuò)展性。AccessDecisionManager協(xié)調(diào)多個AccessDecisionVoter,根據(jù)不同的投票策略做出最終授權(quán)決策,支持從寬松到嚴(yán)格的各種安全需求。內(nèi)置的投票者如RoleVoter和WebExpressionVoter滿足了基本授權(quán)場景,而自定義投票者則使復(fù)雜的業(yè)務(wù)規(guī)則得以實現(xiàn)。方法級安全控制進(jìn)一步擴(kuò)展了授權(quán)能力,使開發(fā)者能夠在業(yè)務(wù)方法層面應(yīng)用精細(xì)的權(quán)限管理。理解并掌握這套授權(quán)機(jī)制,開發(fā)者可以構(gòu)建既安全又靈活的企業(yè)級應(yīng)用,有效平衡安全需求與用戶體驗。在安全威脅日益復(fù)雜的今天,Spring Security的授權(quán)框架為開發(fā)者提供了應(yīng)對挑戰(zhàn)的有力工具,使復(fù)雜的授權(quán)邏輯變得清晰可維護(hù),為應(yīng)用系統(tǒng)的安全基礎(chǔ)奠定了堅實基礎(chǔ)。
到此這篇關(guān)于SpringSecurity授權(quán)機(jī)制的實現(xiàn)(AccessDecisionManager與投票決策)的文章就介紹到這了,更多相關(guān)SpringSecurity授權(quán)機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity實現(xiàn)權(quán)限認(rèn)證與授權(quán)的使用示例
- SpringSecurity進(jìn)行認(rèn)證與授權(quán)的示例代碼
- springSecurity用戶認(rèn)證和授權(quán)的實現(xiàn)
- SpringBoot整合SpringSecurity認(rèn)證與授權(quán)
- 深入淺析springsecurity入門登錄授權(quán)
- SpringSecurityOAuth2實現(xiàn)微信授權(quán)登錄
- SpringBoot+SpringSecurity實現(xiàn)基于真實數(shù)據(jù)的授權(quán)認(rèn)證
- springsecurity第三方授權(quán)認(rèn)證的項目實踐
- SpringSecurity數(shù)據(jù)庫進(jìn)行認(rèn)證和授權(quán)的使用
相關(guān)文章
Spring BeanFactory和FactoryBean區(qū)別解析
這篇文章主要介紹了Spring BeanFactory和FactoryBean區(qū)別解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換的方法
這篇文章主要介紹了SpringBoot加載多個配置文件實現(xiàn)dev、product多環(huán)境切換,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03SpringBoot JPA出現(xiàn)錯誤:No identifier specified&nb
這篇文章主要介紹了SpringBoot JPA出現(xiàn)錯誤:No identifier specified for en解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Java根據(jù)模板實現(xiàn)excel導(dǎo)出標(biāo)準(zhǔn)化
這篇文章主要為大家詳細(xì)介紹了Java如何根據(jù)模板實現(xiàn)excel導(dǎo)出標(biāo)準(zhǔn)化,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,有需要的小伙伴可以參考下2024-03-03將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法
這篇文章主要介紹了將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法,有需要的朋友可以參考一下2014-01-01JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用
今天帶大家學(xué)習(xí)JavaWeb的相關(guān)知識,文章圍繞著JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用展開,文中有非常詳細(xì)的介紹,需要的朋友可以參考下2021-06-06