您好,登錄后才能下訂單哦!
這篇文章主要介紹springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
@Component //用于設(shè)置受保護(hù)資源的權(quán)限信息的數(shù)據(jù)源 public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Bean public AntPathMatcher getAntPathMatcher(){ return new AntPathMatcher(); } @Autowired //獲取數(shù)據(jù)庫(kù)中的保存的url Url表只儲(chǔ)存受保護(hù)的資源,不在表里的資源說明不受保護(hù),任何人都可以訪問 private RightsMapper rightsMapper; @Autowired private AntPathMatcher antPathMatcher; @Override /* * @param 被調(diào)用的保護(hù)資源 * @return 返回能夠訪問該保護(hù)資源的角色集合,如果沒有,則應(yīng)返回空集合。 */ public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; //獲取用戶請(qǐng)求的Url String url = fi.getRequestUrl(); //先到數(shù)據(jù)庫(kù)獲取受權(quán)限控制的Url List<Rights> us = rightsMapper.queryAll(); //用于儲(chǔ)存用戶請(qǐng)求的Url能夠訪問的角色 Collection<ConfigAttribute> rs=new ArrayList<ConfigAttribute>(); for(Rights u:us){ if (u.getUrl() != null) { //逐一判斷用戶請(qǐng)求的Url是否和數(shù)據(jù)庫(kù)中受權(quán)限控制的Url有匹配的 if (antPathMatcher.match(u.getUrl(), url)) { //如果有則將可以訪問該Url的角色儲(chǔ)存到Collection<ConfigAttribute> rs.add(rightsMapper.queryById(u.getId())); } } } if(rs.size()>0) { return rs; } //沒有匹配到,就說明此資源沒有被控制,所有人都可以訪問,返回null即可,返回null則不會(huì)進(jìn)入之后的decide方法 return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { // TODO 自動(dòng)生成的方法存根 return null; } @Override public boolean supports(Class<?> clazz) { // TODO 自動(dòng)生成的方法存根 return FilterInvocation.class.isAssignableFrom(clazz); } }
rights表中的部分內(nèi)容:
表結(jié)構(gòu)
內(nèi)容:
@Component //用于設(shè)置判斷當(dāng)前用戶是否可以訪問被保護(hù)資源的邏輯 public class MyAccessDecisionManager implements AccessDecisionManager { @Override /* * @param 請(qǐng)求該保護(hù)資源的用戶對(duì)象 * @param 被調(diào)用的保護(hù)資源 * @param 有權(quán)限調(diào)用該資源的集合 */ public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { Iterator<ConfigAttribute> ite = configAttributes.iterator(); //遍歷configAttributes,查看當(dāng)前用戶是否有對(duì)應(yīng)的權(quán)限訪問該保護(hù)資源 while (ite.hasNext()) { ConfigAttribute ca = ite.next(); String needRole = ca.getAttribute(); for (GrantedAuthority ga : authentication.getAuthorities()) { if (ga.getAuthority().equals(needRole)) { // 匹配到有對(duì)應(yīng)角色,則允許通過 return; } } } // 該url有配置權(quán)限,但是當(dāng)前登錄用戶沒有匹配到對(duì)應(yīng)權(quán)限,則禁止訪問 throw new AccessDeniedException("not allow"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailsService myUserDetailsService; @Autowired private SendSmsSecurityConfig sendSmsSecurityConfig; @Autowired private MyAccessDecisionManager myAccessDecisionManager; @Autowired private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource; //加密機(jī)制 @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); // 不加密 } //認(rèn)證 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService) .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()//對(duì)請(qǐng)求授權(quán) .antMatchers("/**").permitAll() .anyRequest()//任何請(qǐng)求 .authenticated()//登錄后訪問 .withObjectPostProcessor( new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess( O fsi) { fsi.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource); fsi.setAccessDecisionManager(myAccessDecisionManager); return fsi; } }) .and().csrf().disable(); } }
配置如下代碼:
至此完成所有配置?。?!
自定義權(quán)限認(rèn)證,一部分接口必須要有相應(yīng)的角色權(quán)限,一部分接口面向所有訪問者,一部分接口任何人都不能訪問。但是在使用 SpringSecurity的過程中發(fā)現(xiàn),框架會(huì)將沒有指定角色列表的URL資源直接放行,不做攔截。
用戶登錄認(rèn)證成功后,攜帶Token訪問URL資源,spring security 根據(jù)Token(請(qǐng)求頭Authorization中)來(lái)分辨不同用戶。
用戶權(quán)限數(shù)據(jù)源是一個(gè)Map:以 URL資源為Key,以有權(quán)訪問的Key的角色列表為Value。
使用時(shí)發(fā)現(xiàn)當(dāng)一個(gè)接口有Key,但是Value為空或null時(shí),spring security 框架自動(dòng)放行,導(dǎo)致了權(quán)限失效問題。
第一種方法:
默認(rèn)rejectPublicInvocations為false。
對(duì)需要控制權(quán)限的URL資源添加標(biāo)志,以防止roleList為空,跳過了權(quán)限驗(yàn)證.
公共權(quán)限設(shè)置為null,不進(jìn)行權(quán)限驗(yàn)證
第二種方法:
配置rejectPublicInvocations為true
此后roleList為空,或者沒有找到URL資源時(shí),都為拒絕訪問
需要控制權(quán)限的URL資源,即使對(duì)應(yīng)角色為空,也會(huì)進(jìn)行權(quán)限驗(yàn)證
公共權(quán)限設(shè)置為所有角色和匿名角色,不進(jìn)行權(quán)限驗(yàn)證
package org.springframework.security.access.intercept; /** * 對(duì)安全對(duì)象(訪問請(qǐng)求+用戶主體)攔截的抽象類源碼 */ public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware { // ... 其他方法省略 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()); } // 從權(quán)限數(shù)據(jù)源獲取了當(dāng)前 <URL資源> 對(duì)應(yīng)的 <角色列表> Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); // 框架在此處判斷URL資源對(duì)應(yīng)的角色列表是否為空 if (attributes == null || attributes.isEmpty()) { // rejectPublicInvocations默認(rèn)為false // 可以配置為true,即角色列表為空的時(shí)候不進(jìn)行放行 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); } // 如果當(dāng)前用戶權(quán)限對(duì)象為null if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage( "AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } Authentication authenticated = authenticateIfRequired(); // Attempt authorization,此處調(diào)用accessDecisionManager 進(jìn)行鑒權(quán) try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } if (publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user,這里可以另外配置或修改用戶的權(quán)限對(duì)象,特殊場(chǎng)景使用 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(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } } // ... 其他方法略 }
以上是“springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。