溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證

發(fā)布時(shí)間:2021-06-22 13:05:09 來(lái)源:億速云 閱讀:415 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

1、MyFilterInvocationSecurityMetadataSource 類判斷該訪問路徑是否被保護(hù)

@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)

springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證

內(nèi)容:

springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證

2、MyAccessDecisionManager 類判斷該用戶是否有權(quán)限訪問

@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;
    }
}

3、在SecurityConfig 類中配置說明

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

配置如下代碼:

springboot+springsecurity怎么實(shí)現(xiàn)動(dòng)態(tài)url細(xì)粒度權(quán)限認(rèn)證

至此完成所有配置?。?!

SpringSecurity解決公共接口自定義權(quán)限驗(yàn)證失效問題,和源碼分析

背景:

自定義權(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è)資訊頻道!

向AI問一下細(xì)節(jié)

免責(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)容。

AI