日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Spring Security之基于方法配置權限

作者:Evan_L 更新時間: 2024-07-18 編程語言

前言

Spring Security有兩種配置方式,今天重點是紹基于方法配置的方式。

基于方法配置權限

這個主要是有一些注解提供給大家使用,今天來給大家一個demo(參考自官方sample)。

maven就不多累贅了。重點看看配置。

  • 基于角色配置
/**
 * 啟用方法安全:@EnableMethodSecurity
 * 
 * 啟用@secured注解: **securedEnabled = true**
 * <p>會導入配置:SecuredMethodSecurityConfiguration<p>
 * 
 * 啟用@PreAuthorize@PostAuthorize@PreFilter@PostFilter:**prePostEnabled = false**
 * <p>會導入配置:PrePostMethodSecurityConfiguration</p>
 * 
 * 啟用jsr250相關的安全注解:**jsr250Enabled = true**
 * <p>會導入配置:Jsr250MethodSecurityConfiguration</p>
 * <p>jsr250包括@RolesAllowed@PermitAll@DenyAll</p>
 */ 
@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class AspectjSecurityConfig {

}

/**
 * 這個類的所有方法都需要有ROLE_USER角色才能執行
 */
@Service
@Secured("ROLE_USER")
public class SecuredService {

	public void secureMethod() {
		// nothing
	}

}

@Service
public class SecuredService {
    

	public void publicMethod() {
		// nothing
	}
    /**
     * 這個方法需要ROLE_USER才能執行
     * 這三種配置方式都是等價的,只是提供支持的Advice不同。
     */ 
	@Secured("ROLE_USER")
    // @RolesAllowed("ROLE_USER")
    // @PreAuthorize("hasRole('ROLE_USER')")
	public void secureMethod() {
		// nothing
	}

	@PreAuthorize("arguments[0] ne 'tony'")
	// @PreAuthorize("filterObject ne 'tony'")
    public void preAuthorize(@RequestParam("userCode") String userCode) {
        // @preAuthorize不會賦值ROOT的filterObject和returnObject,因此無法使用入參。只能使用MethodSecurityExpressionRoot的其他方法
		// 這個方法的實驗現象為:不管傳什么都能通過表達式
        logger.info("preAuthorize:{}", userCode);
        // nothing
    }

    /** 	
     * 這個入參會被過濾地只剩下與authentication.name一樣的
     */ 
	@PreFilter("filterObject == authentication.name")
	public void preFilter(@RequestParam("userCodeList") List<String> userCodeList) {
		// http://localhost:8090/foo/preFilter?userCodeList=leo,tony
        logger.info("preAuthorize:{}", userCode);
	}

    /**
     * 這個方法需要ROLE_USER才能執行
     */ 
	@PostAuthorize("returnObject ne 'tony'")
	public String postAuthorize(@RequestParam("userCode") String userCode) {
		// 傳入的是不是tony,會拋出異常: 403
        logger.info("postAuthorize:{}", userCode);
		return userCode;
	}

    @GetMapping("/preAuthorizeSpel")
    @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
    public void preAuthorizeHasRole() {
        logger.info("preAuthorizeSpel:{}", userCode);
        // nothing
    }

    /**
     * 這個方法需要ROLE_USER才能執行
     */ 
	@PostFilter("returnObject == authentication.name")
	public List<String> PostFilter(@RequestParam("userCodeList") List<String> userCodeList) {
		// 實驗結果就是,傳入了的參數有值,但只有跟用戶名一樣的才會返回
        logger.info("PostFilter:{}", userCodeList);
		return userCodeList;
	}

}

以上就是怎么使用,接著我們看下是如何實現的。

基于方法授權方式的實現原理

前面我們說過,是基于AOP實現的。那么,現在我們從源碼層面來看看。

我們可以看到上面按照@EnableMethodSecurity的配置,分別對應地導入三個配置。
但是,我們可以先從AOP的角度設想一下,我們需要的是哪種類型的通知?不妨歸類一下:

注解類型 通知類型 描述/備注
@PreFilter@PreAuthorize@Secured以及JSR-250的相關注解 前置通知 這些很明顯都是需要先校驗/過濾參數再執行目標方法
@PostFilter@PostAuthorize 后置通知 先執行目標方法,再校驗/過濾結果集

接著我們來看看Spring Security的設計:

AuthorizationManagerBeforeMethodInterceptor

在執行方法之前進行鑒權,這也意味著,當權限不足時,他會拋出異常。
實際上,他是一個通用的增強,全取決于你怎么使用它。
在SpringSecurity的配置里,他可以負責@PreAuthor,也可以負責@Secured,甚至還能負責jsr250的相關注解。
需要提醒一點,一個實例對象只能一種注解哈。

但是,大家有沒有想過一個問題:為什么@PreAuthor@Secured以及jsr250的相關注解,都能交給他來處理?
又或者說,他的設計是如何將這三者的處理抽象統一起來的?更具體一點,此三者有何共同之處,可以進行抽象和統一的??

要回答這個問題,我們需要先從這個三者的入手:
共同點:

都需要在執行方法前進行權限校驗,校驗不通過則都需要拋出異常,阻斷方法調用。

異同點:

注解 配置描述 執行
@PreAuthorize 配置的是SPEL表達式 通過執行表達式來得出是否滿足訪問權限
@Secured 指定角色 需要校驗當前用戶是否擁有指定角色
jsr250的@PermitAll - 任何人都可以訪問
jsr250的@DenyAll - 任何人都不能訪問
jsr250的@RolesAllowed 指定角色 需要校驗當前用戶是否擁有指定角色

從共同點出發,本質上無非就是鑒權嘛,這不是很符合Spring Security的AuthorizationManager的職責嗎?
然后就是不同點,我們發現無非就是權限的配置來源不同需要解析不同的注解咯。
有了這個思路,接下來我們翻找源碼,理解起來就容易多了。

public final class AuthorizationManagerBeforeMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
	// 構造器,需要傳入Pointcut,和AuthorizationManager<MethodInvocation>,以便校驗權限。
	public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
			AuthorizationManager<MethodInvocation> authorizationManager) {
		Assert.notNull(pointcut, "pointcut cannot be null");
		Assert.notNull(authorizationManager, "authorizationManager cannot be null");
		this.pointcut = pointcut;
		this.authorizationManager = authorizationManager;
	}

	/**
	 * 創建負責處理@PreAuthorize的增強
	 */
	public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
			PreAuthorizeAuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
		return interceptor;
	}

	/**
	 * 創建負責處理@Secured
	 */
	public static AuthorizationManagerBeforeMethodInterceptor secured(
			SecuredAuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());
		return interceptor;
	}

	/**
	 * 創建負責處理jsr250相關注解: @RolesAllow@PermitAll@DenyAll
	 */
	public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) {
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
				AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),
				authorizationManager);
		interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());
		return interceptor;
	}
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 校驗權限
		attemptAuthorization(mi);
		return mi.proceed();
	}
	
	private void attemptAuthorization(MethodInvocation mi) {
		// 通過AuthorizationManager校驗權限,這意味者AuthorizationManager必須具備解析相關注解的能力,實際上他交給了另外一個組件,后面會說到。
		AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
		// 發布授權事件
		this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
		// 不通過就拋出訪問拒絕異常
		if (decision != null && !decision.isGranted()) {
			throw new AccessDeniedException("Access Denied");
		}
	}

從源碼,我們可以發現之前分析的三類注解,只需要一個MethodInterceptor。而他們的不同之處則由AuthorizationManager這個同一個的接口進行統一調度。
對應地:

注解 AuthorizationManager
@PreAuthorize PreAuthorizeAuthorizationManager
@Secured SecuredAuthorizationManager
jsr250的注解 Jsr250AuthorizationManager
  • SecuredAuthorizationManager:
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
	// 這是個最為簡單的AuthorizationManager實現,無非就是將當前用戶的權限與所需要的權限進行比較,如果找到就認為擁有訪問權限。
	private AuthorizationManager<Collection<String>> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
	// 緩存已經解析過的方法所對應的權限
	private final Map<MethodClassKey, Set<String>> cachedAuthorities = new ConcurrentHashMap<>();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		// 獲取目標方法配置的訪問權限
		Set<String> authorities = getAuthorities(mi);
		// 比較權限,從authoritiesAuthorizationManager.check方法的入參也能大概猜到怎么實現
		return authorities.isEmpty() ? null : this.authoritiesAuthorizationManager.check(authentication, authorities);
	}

	private Set<String> getAuthorities(MethodInvocation methodInvocation) {
		Method method = methodInvocation.getMethod();
		Object target = methodInvocation.getThis();
		Class<?> targetClass = (target != null) ? target.getClass() : null;
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 如果尚未存在相關緩存,則進行解析。
		// 由此可見,只有當目標方法被第一次執行/調用的時候才會出發解析動作
		return this.cachedAuthorities.computeIfAbsent(cacheKey, (k) -> resolveAuthorities(method, targetClass));
	}

	private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		// 嘗試在方法上尋找目標注解@Secured
		Secured secured = findSecuredAnnotation(specificMethod);
		// 返回@Secured注解所配置的權限
		return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();
	}
}
  • Jsr250AuthorizationManager
    相較于SecuredAuthorizationManager,他負責的則是3個注解,而不是一個。
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
	// 這是AuthorizationManager的注冊器
	// 因為要處理三個注解,每個注解的處理邏輯雖然簡單,但是確實不一樣。
	// 而每個方法存在的注解也不一樣
	private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
		// 根據方法,從注冊器中獲取對應的AuthorizationManager
		AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
		// 執行授權校驗邏輯
		return delegate.check(authentication, methodInvocation);
	}

	// 這是Jsr250AuthorizationManager的內部類
	private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
		@NonNull
		@Override
		AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
			// 解析注解
			Annotation annotation = findJsr250Annotation(method, targetClass);
			if (annotation instanceof DenyAll) {
				// 返回一個lambda表達式構建的AuthorizationManager,其實現為:直接返回拒絕訪問
				return (a, o) -> new AuthorizationDecision(false);
			}
			if (annotation instanceof PermitAll) {
				// 返回一個lambda表達式構建的AuthorizationManager,其實現為:直接返回允許訪問
				return (a, o) -> new AuthorizationDecision(true);
			}
			if (annotation instanceof RolesAllowed) {
				RolesAllowed rolesAllowed = (RolesAllowed) annotation;
				// 返回與@Secured一樣的AuthorityAuthorizationManager
				return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
						rolesAllowed.value());
			}
			// 這里應當看到,這三個注解的判斷順序。事實上,在解析注解的時候,只會返回其中之一。
			// 而jsr250的注解也是不能同時使用的,只能用其中一個。
			return NULL_MANAGER;
		}
	}
}

abstract class AbstractAuthorizationManagerRegistry {
	private final Map<MethodClassKey, AuthorizationManager<MethodInvocation>> cachedManagers = new ConcurrentHashMap<>();

	final AuthorizationManager<MethodInvocation> getManager(MethodInvocation methodInvocation) {
		Method method = methodInvocation.getMethod();
		Object target = methodInvocation.getThis();
		Class<?> targetClass = (target != null) ? target.getClass() : null;
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 這里就跟@Secured類似了,只不過其需要兼顧3個注解,因此value變成了AuthorizationManager
		return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
	}	
	
	abstract AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass);
}

  • PreAuthorizeAuthorizationManager
    與他的同伴不同,他可是要支持SPEL的。
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
	private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		// 通過方法,從注冊器獲取ExpressionAttribute。
		ExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return null;
		}
		// 通過注冊器找到ExpressionHandler,并創建EvaluationContext
		EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
		// 執行SPEL表達式
		boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
		// 返回決策:是否允許訪問
		return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
	}
}

這里我們看到了為了支持SPEL的第一個抽象:ExpressionAttribute。他最重要的使命就是記錄表達式。
再來看看,最為重要的:PreAuthorizeExpressionAttributeRegistry

final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
	private final MethodSecurityExpressionHandler expressionHandler;

	@Override
	ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		// 尋找@PreAuthorize注解
		PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
		if (preAuthorize == null) {
			return ExpressionAttribute.NULL_ATTRIBUTE;
		}
		// 解析表達式
		Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
			.parseExpression(preAuthorize.value());
		// 返回解析到的表達式
		return new ExpressionAttribute(preAuthorizeExpression);
	}
}

abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
	// 緩存方法對應的表達式
	private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();

	final T getAttribute(Method method, Class<?> targetClass) {
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		// 解析并緩存表達式
		return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
	}
}

AuthorizationManagerAfterMethodInterceptor

他與前面介紹的MethodInterceptor相呼應,一前一后,你從名字就能發現。只不過與前者相比,目前他只有一個注解需要關注@PostAuthorize。

public final class AuthorizationManagerAfterMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {

	private final AuthorizationManager<MethodInvocationResult> authorizationManager;
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 先執行
		Object result = mi.proceed();
		// 再校驗。這里應當注意到他的入參包括方法返回值
		attemptAuthorization(mi, result);
		return result;
	}
	
	private void attemptAuthorization(MethodInvocation mi, Object result) {
		// 熟悉的配方,只不過入參變成了方法返回值罷了
		MethodInvocationResult object = new MethodInvocationResult(mi, result);
		AuthorizationDecision decision = this.authorizationManager.check(this.authentication, object);

		this.eventPublisher.publishAuthorizationEvent(this.authentication, object, decision);
		if (decision != null && !decision.isGranted()) {
			throw new AccessDeniedException("Access Denied");
		}
	}
}

public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {

	private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
	
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult mi) {
		ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return null;
		}

		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
		expressionHandler.setReturnObject(mi.getResult(), ctx);

		boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
		return new ExpressionAuthorizationDecision(granted, attribute.getExpression());
	}
}

經過了前面的分析,這里也就沒啥多說的了。與@PreAuthorize很相似。

PreFilterAuthorizationMethodInterceptor

負責處理@PreFilter注解的方法調用。

public final class PreFilterAuthorizationMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {

	public PreFilterAuthorizationMethodInterceptor() {
		// 默認處理的是@PreFilter
		this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
	}
	// ...

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 從屬性注冊器中獲取到目標方法解析好的@PreFilter的相關屬性信息。
		PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
			return mi.proceed();
		}
		// 從屬性注冊器中獲取對應的SPEL的表達式處理器,以便創建SPEL的上下文
		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
		// 從表達式上下文中獲取需要過濾的目標
		Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);
		// 根據SPEL表達式執行過濾
		expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
		// 執行目標方法
		return mi.proceed();
	}

	// ...
}

可以看出,與@PreAuthorize類似,只是少了一層AuthorizationManager的封裝。原因也很簡單,AuthorizationManager是用來鑒權的,而@PreFilter不需要鑒權。
只需要過濾參數即可。因此他也不會拋出訪問異常。

PostFilterAuthorizationMethodInterceptor

負責處理@PostFilter。

public final class PostFilterAuthorizationMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
	// 這個注冊器與PreAuthorizeExpressionAttributeRegistry類似,都繼承同一個父類。
	// 只有解析的注解不一樣,對應ExpressionAttribute稍稍不一樣,前者記錄有方法參數,而后者記錄有方法返回值。
	private PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();

	public PostFilterAuthorizationMethodInterceptor() {
		this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
	}
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 先執行了方法,獲得返回值
		Object returnedObject = mi.proceed();
		ExpressionAttribute attribute = this.registry.getAttribute(mi);
		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
			return returnedObject;
		}
		// 獲取ExpressionHandler,創建EvaluationContext
		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
		EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
		// 執行過濾邏輯
		return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
	}

我們可以看到@PostFilter,只是對返回進行過濾,同樣也不會拋出訪問異常。

小結

分析完源碼之后,我們不難發現,每一個類都很小,且每一個方法都足夠簡單。這點是值得大家學習的。
當然,不能盲目。因為作為一款優秀的框架,必須要具備良好的可擴展性。通過分析和抽象,能夠很好起到這個作用。這是框架源碼設計者所追求的。
但是其弊端也很明顯,那就是一個完整的功能實現,往往需要諸多組件協作完成,初學者很難入門。因此,在實際業務開發過程中,不能盲目地追求這種擴展性。
同時,面對復雜業務時,不妨多分析,是否可以像框架源碼的設計者那樣思考?當然,設計完成后,需要留下相當的文檔。

總結

各方法注解的應用場景

注解 應用場景 實現原理
@Secured 配置需要滿足的權限,可以是角色名或者權限 基于AuthorityAuthorizationManager
JSR-250的@PermitAll 任何人都可以訪問 基于lambda表達式實現的簡單的AuthorizationManager
JSR-250的@DenyAll 任何人都不能訪問 基于lambda表達式實現的簡單的AuthorizationManager
JSR-250的@RolesAllowed 配置需要滿足的權限,可以是角色名或者權限。可以于@Secured相互取代。 基于AuthorityAuthorizationManager,可以于@Secured相互取代。
@PreAuthorize 用于在方法調用之前鑒權,支持方法入參作為表達式的一部分 基于SPEL表達式
@PostAuthorize 用于在方法調用之后鑒權,支持方法返回值作為表達式的一部分 基于SPEL表達式
@PreFilter 用于過濾方法入參 基于SPEL表達式
@PostFilter 用于過濾方法返回值 基于SPEL表達式

在搞清楚了各注解的用處和使用后,我們在回過頭來看,我們可以使用哪些授權方式:

  1. RBAC(基于角色的訪問控制)
    這個除了@PreFilter@PostFilter,其他注解都能實現。SPEL可以使用hasAnyRole("ADMIN","USER")

后記

至此,我們應該算全方面聊完了基于方法注解怎么配置權限這個話題。不管各個注解的使用場景,亦或者其實現原理,還是各種設計思路。
下一節,我們將聊聊基于HttpRequest的權限配置方式。這個直接使用倒是簡單,但是想要定制就必須深入理解其設計和原理。加油。

參照

Method Security

Spring Security的@PreAythorize、@PostAuthorize、@PreFilter 和@PostFilter

原文鏈接:https://blog.csdn.net/Evan_L/article/details/136605487

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新