Подробный анализ процесса аутентификации SpringSecurity сделан, чтобы вы поняли, как он выполняется!

Spring Boot задняя часть
Подробный анализ процесса аутентификации SpringSecurity сделан, чтобы вы поняли, как он выполняется!

Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

На бумаге в конце концов я чувствую себя мелким, и я абсолютно точно знаю, что это дело должно быть сделано.

Прочтите эту статью:

Для простого использования 👉:SpringBoot интегрирует SpringSecurity в качестве фреймворка безопасности с исходным кодом.

Вы можете получить: 🛴

  1. можно примерно понятьSpringSecurity Процесс аутентификации.
  2. Умею отлаживать пошагово умею рисоватьSpringSecurityБлок-схема аутентификации.
  3. заSpringSecurity Фреймворк будет иметь более глубокое понимание и сможет достичь более высокой степени настройки при его использовании.
  4. и правильноSpringSecurity Более глубокое мышление

Введение:

xdm, я так и не научился писать короткие рассказы 😭, могу только пригласить вас попить здесь колу 🥤, пожалуйста, xdm вознаградит вас одним кликом и тремя подряд 😁.

image-20211015195832816

Xdm, когда вы используете структуру безопасности SpringSecurity, задумывались ли вы когда-нибудь об отладке шаг за шагом, чтобы увидеть, как она реализована, чтобы определить, доступна ли она?

следующее:

@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/role/admin1")
String admin() {
    return "role: ROLE_ADMIN";
}

Почему мы можем написать эту аннотацию? Как судить об этом?

Я уже писал это однажды 👨‍💻Анализ процесса входа в Spring Security, я написал эту статью, чтобы написать 👩‍💻Spring Security реализует несколько методов входа в систему.Сделайте предзнаменование.

Так в чем причина написания этой статьи?

Увидел моего друга в Наггетси ушинаписаноАнализ процесса динамической аутентификации Spring Security, я понял, что использование аннотаций на самом деле не очень хорошая штука, это прямо закреплено в проекте и не может быть динамически изменено. В последние два дня я также собираюсь реализовать небольшую демонстрацию динамической аутентификации Security.

xdm, обязательно запомните,纸上得来终觉浅,绝知此事要躬行, особенно отладочные статьи всю дорогу лично на яму наступали.

Для технологии знание того, как ее использовать, означает, что у нас уже есть простое представление о ней, и мы можем использовать ее лучше, если у нас есть четкое понимание контекста и деталей.

Далее, пусть 👨‍🏫 покажет вам все.

2. Блок-схема:

На следующем рисунке показана схема безопасности, найденная в Baidu.

image-20211015202632522

Блок-схема, которую я рисую дальше, основана на состоянии, когда пользователь уже вошел в систему.

Весь процесс аутентификации был сосредоточен на зеленой части цепочки фильтров на рисунке, а аутентификация, о которой мы будем говорить сегодня, в основном вращается вокруг оранжевой части, которая отмечена на рисунке:FilterSecurityInterceptor.

Это также начало моей блок-схемы, как показано ниже:

image-20211016152246436

Если на изображении выше есть какие-либо неуместности, пожалуйста, исправьте их, большое спасибо.

Что касается приблизительного объяснения приведенного выше рисунка, я вернусь по порядку позже:

1. После входа в систему пользователь получает доступ к интерфейсу, который требует разрешений, проходит через ряд фильтров и достигаетFilterSecurityInterceptor, Метод invoke() класса FilterSecurityInterceptor выполняет определенное поведение перехвата, в частности, три метода beforeInvocation, finallyInvocation и afterInvocation, которые определены в родительском классе.AbstractSecurityInterceptorсередина.

2. Вызовите метод beforeInvocation класса AbstractSecurityInterceptor.AbstractSecurityInterceptorОбеспечит правильную стартовую конфигурацию перехватчика безопасности. Также будет реализована правильная обработка обращений к защищенным объектам, а именно:

  1. Получите разрешения, необходимые для доступа к текущему ресурсуSecurityMetadataSource..getAttributes(object); вернуть атрибуты Collection
  2. Получить объект аутентификации из SecurityContextHolder. `Аутентификация аутентифицирована = authenticationIfRequired();
  3. попробуй авторизоватьсяattemptAuthorization(object, attributes, authenticated); Вызовите метод решения интерфейса AccessDecisionManager для выполнения проверки подлинности.Если проверка подлинности не удалась, будет создано исключение напрямую.
  4. Возвращает InterceptorStatusToken.

3. После невыразимых трудностей достигается метод MethodSecurityInterceptor, и он снова вызывает метод AbstractSecurityInterceptor.beforeInvocation(mi) для проверки разрешений.

  • Во время аутентификации голосующий будет замененPreInvocationAuthorizationAdviceVoter

Прежде чем зайти в тему, поставьте картинку на время:

123456.jpg

当乌云和白云相遇


👨‍💻

image-20211015201424942

Третий, первая половина

Первая половина функции предназначена для определения статуса пользователя, а не для выполнения аутентификации, но они очень похожи. Обнаружение аннотаций в методах находится во второй половине.

1) Запись: FilterSecurityInterceptor

Первый шаг: FilterSecurityInterceptor void doFilter (запрос ServletRequest, ответ ServletResponse, цепочка FilterChain)

//过滤器链实际调用的方法。 简单地委托给invoke(FilterInvocation)方法。
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    invoke(new FilterInvocation(request, response, chain));
}

Затем посмотрите на void invoke (FilterInvocation filterInvocation)

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    if (isApplied(filterInvocation) && this.observeOncePerRequest) {
		//过滤器已应用于此请求,用户希望我们观察每个请求处理一次,因此不要重新进行安全检查
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        return;
    }
    // 第一次调用这个请求,所以执行安全检查
    if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
        filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    }
    
    //调用 beforeInvocation(filterInvocation) 方法 跟着这个方法往下看
    InterceptorStatusToken token = super.beforeInvocation(filterInvocation) ;
    try {
        //每个过滤器都有这么一步 
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    }
    finally {
        //在安全对象调用完成后清理AbstractSecurityInterceptor的工作。
        //无论安全对象调用是否成功返回,都应该在安全对象调用之后和 afterInvocation 之前调用此方法(即它应该在 finally 块中完成)。
        super.finallyInvocation(token);
    }
    //当调用afterInvocation(InterceptorStatusToken,Object)时,AbstractSecurityInterceptor不会采取进一步的操作。
    super.afterInvocation(token, null);
}

2) Введите: AbstractSecurityInterceptor

Проверка авторизации методом beforeInvocation()

Шаг 2: super.beforeInvocation(filterInvocation); Некоторая печатная информация сокращена, слишком длинна для чтения

protected InterceptorStatusToken beforeInvocation(Object object) {
    	//检查操作
		Assert.notNull(object, "Object was null");
		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
			//....
		}
    	//这里获取的信息看下图示1 :
    	//object 就是调用处传过来的参数 FilterInvocation filterInvocation,它本身其实就是 HttpServletRequest 和 HttpServletResponse 的增强
    	//object :filter invocation [GET /role/admin1] "
   		//然后我们获取到的就是受保护调用的列表  
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
		if (CollectionUtils.isEmpty(attributes)) {
            //...
			return null; // no further work post-invocation
		}
    	//在 SecurityContext 中未找到身份验证对象,会发事件抛异常
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"), object, attributes);
		}
    
    	//在这里拿到了  Authentication 对象登录的信息 ,后文会简单说是如何拿到的
		Authentication authenticated = authenticateIfRequired();
		if (this.logger.isTraceEnabled()) {
			this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
		}
		// Attempt authorization : 尝试授权 这步本文重点,用我的话来说,这就是鉴权的入口  重点关注,下文继续
		attemptAuthorization(object, attributes, authenticated);
		//...
		// Attempt to run as a different user
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
		if (runAs != null) {
			//...
		}
		// 无后续动作
		return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

	}

оCollection< ConfigAttribute > attributes = this.obtainSecurityMetadataSource().getAttributes(object);этот код.

Приехав сюда в первый раз,FilterSecurityInterceptorОтSecurityMetadataSourceподклассDefaultFilterInvocationSecurityMetadataSource Получены текущие данные. Это очень отличается от нашего второго раза здесь. Выражение здесьauthenticated, перевод заверенный.

image-20211016160929332

Сравнение будет позже.

Смотрим вниз:Аутентификация authenticationateIfRequired() Получить идентификационную информацию

//如果Authentication.isAuthenticated()返回 false 或属性alwaysReauthenticate已设置为 true,
//则检查当前的身份验证令牌并将其传递给 AuthenticationManager进行身份验证
private Authentication authenticateIfRequired() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
        return authentication;
    }
    authentication = this.authenticationManager.authenticate(authentication);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return authentication;
}

3) Попытка авторизации: tryAuthorization()

Шаг 3: Попытка авторизации: tryAuthorization()

private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
                                  Authentication authenticated) {
    try {
        //接着套娃 我们去看 AccessDecisionManager 下的 decide() 方法
        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;
    }
}

Описание решающего устройства AccessDecisionManager:

this.accessDecisionManager на самом деле является интерфейсом. Давайте посмотрим на его исходный код

public interface AccessDecisionManager {

	/**
为传递的参数解析访问控制决策。
参数:
身份验证 - 调用方法的调用者(非空)
object – 被调用的安全对象
configAttributes – 与被调用的安全对象关联的配置属性
	 */
	void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException;


    // 下面这两个方法主要起辅助作用的。大都执行检查操作
	boolean supports(ConfigAttribute attribute);

	boolean supports(Class<?> clazz);

}

Давайте сначала посмотрим на структуру интерфейса, а затем посмотрим, как реализован внутренний механизм аутентификации класса его реализации, какую информацию нужно получить и как судить, может ли она пройти.

мы можем видеть этоAccessDecisionManagerИнтерфейс, под интерфейсом есть абстрактный класс, а затем есть три класса реализации.

image-20211015132038199

Они представляют разные механизмы соответственно.

  1. AffirmativeBased: Предоставляет доступ, если какой-либо AccessDecisionVoter возвращает положительный ответ. То есть, если есть один голос, его можно пройти, и по умолчанию это он.
  2. Основанный на консенсусе: меньшинство подчиняется большинству. Большинство голосов согласны пройти. Как демократические выборы.
  3. UnanimousBased: требует, чтобы все избиратели воздержались или предоставили доступ. упоминается как один голос против. Пока есть хотя бы один голос против, он не может пройти.

Давайте посмотрим на AffirmativeBased по умолчанию:

public class AffirmativeBased extends AbstractAccessDecisionManager {

	public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
		super(decisionVoters);
	}

	/**
	这个具体的实现只是轮询所有配置的AccessDecisionVoter并在任何AccessDecisionVoter投赞成票时授予访问权限。 仅当存在拒绝投票且没有赞成票时才拒绝访问。
如果每个AccessDecisionVoter放弃投票,则决策将基于isAllowIfAllAbstainDecisions()属性(默认为 false)。
	 */
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException {
		int deny = 0;
		for (AccessDecisionVoter voter : getDecisionVoters()) {
			int result = voter.vote(authentication, object, configAttributes);
			switch (result) {
			case AccessDecisionVoter.ACCESS_GRANTED:
				return;
			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();
	}
}

Здесь он будет задействован сноваAccessDecisionVoterВыходят, то есть избиратели, которые могут голосовать.

Интерфейс аудитории для голосования AccessDecisionVoter

Давайте сначала посмотрим на его исходный код, а затем посмотрим на его класс реализации:

//表示一个类负责对授权决定进行投票。
//投票的协调(即轮询AccessDecisionVoter ,统计他们的响应,并做出最终授权决定)由AccessDecisionManager执行。
public interface AccessDecisionVoter<S> {

    int ACCESS_GRANTED = 1;

    int ACCESS_ABSTAIN = 0;

    int ACCESS_DENIED = -1;

    //这两个用来执行check操作,判断参数是否合法等等
    boolean supports(ConfigAttribute attribute);
    boolean supports(Class<?> clazz);

    /**
指示是否授予访问权限。
决定必须是肯定的 ( ACCESS_GRANTED )、否定的 ( ACCESS_DENIED ) 或者 AccessDecisionVoter可以弃权 ( ACCESS_ABSTAIN ) 投票。 
在任何情况下,实现类都不应返回任何其他值。 如果需要对结果进行加权,则应改为在自定义AccessDecisionManager处理。
除非AccessDecisionVoter由于传递的方法调用或配置属性参数而专门用于对访问控制决策进行投票,否则它必须返回ACCESS_ABSTAIN 。 
这可以防止协调AccessDecisionManager计算来自那些AccessDecisionVoter的选票,而这些AccessDecisionVoter对访问控制决策没有合法利益。
虽然安全对象(例如MethodInvocation )作为参数传递以最大限度地提高访问控制决策的灵活性,但实现类不应修改它或导致所表示的调用发生(例如,通过调用MethodInvocation.proceed() ) .
	 */
	int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}

Посмотрим на его структуру:

image-20211015133632166

  1. RoleVoter В основном используется для определения того, имеет ли текущий запрос роль, требуемую интерфейсом.
  2. RoleHierarchyVoter Это подкласс RoleVoter. На основе оценки ролей RoleVoter он вводит иерархическое управление ролями, то есть наследование ролей.
  3. WebExpressionVoter Это избиратель управления разрешениями на основе выражений.
  4. Jsr250Voter Избиратель, который обрабатывает аннотации разрешений Jsr-250, такие как@PermitAll,@DenyAllЖдать.
  5. AuthenticatedVoterИспользуется для определения того, имеет ли ConfigAttribute три роли: IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, IS_AUTHENTICATED_ANONYMOUSLY.
  6. AbstractAclVoter Предоставляет вспомогательные методы для записи параметров ACL для объектов домена, не привязанных к какой-либо конкретной системе ACL.
  7. PreInvocationAuthorizationAdviceVoter Разрешения, обрабатываемые с помощью аннотаций @PreFilter и @PreAuthorize, черезPreInvocationAuthorizationAdvice разрешить.

AffirmativeBasedПо умолчанию передается только один конструкторWebExpressionVoter, этот конструктор логически обработает результат голосования в соответствии с вашей конфигурацией в конфигурационном файле.

Поэтому, когда мы выполняем первый цикл, мы также имеем дело с ним здесь.

public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
	private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();

	@Override
	public int vote(Authentication authentication, FilterInvocation filterInvocation,
			Collection<ConfigAttribute> attributes) {
        //...执行的一些检查
        //
		WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
		if (webExpressionConfigAttribute == null) {
			return ACCESS_ABSTAIN;
		}
        //允许对EvaluationContext进行后处理。 实现可能会返回一个新的EvaluationContext实例或修改传入的EvaluationContext 。
        EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
            //调用内部模板方法来创建StandardEvaluationContext和SecurityExpressionRoot对象。
            this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
        //针对指定的根对象评估默认上下文中的表达式。 如果评估结果与预期结果类型不匹配(并且无法转换为),则将返回异常。
        boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
		// 投赞同票,返回
        if (granted) {
			return ACCESS_GRANTED;
		}
		return ACCESS_DENIED;
	}

    //循环判断
	private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
		for (ConfigAttribute attribute : attributes) {
			if (attribute instanceof WebExpressionConfigAttribute) {
				return (WebExpressionConfigAttribute) attribute;
			}
		}
		return null;
	}
    //...
}

image-20211016163159648

Данные здесь тоже одинаковые, и мы соответствуем друг другу выше.

4) Процесс возврата

4.1 Сначала вернитесь к методу AffirmativeBased.decide(), проголосуйте за прохождение и продолжите повторный запуск

for (AccessDecisionVoter voter : getDecisionVoters()) {
    int result = voter.vote(authentication, object, configAttributes);
    switch (result) {
        case AccessDecisionVoter.ACCESS_GRANTED:
            return;
        case AccessDecisionVoter.ACCESS_DENIED:
            deny++;
            break;
        default:
            break;
    }
}

4.2 Возврат в место вызова метода AbstractSecurityInterceptor, здесь нет возвращаемого значения, возврат непосредственно вbeforeInvocationметод.

private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
                                  Authentication authenticated) {
    try {
        this.accessDecisionManager.decide(authenticated, object, attributes);
    }
}

4.3 Вернуться кbeforeInvocationметод,

protected InterceptorStatusToken beforeInvocation(Object object) {
    // 返回到这里,我们再顺着往下看,看如何执行	
    attemptAuthorization(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);
        // 需要恢复到 token.Authenticated 调用后 true的意思是:如果能以其他用户运行 就执行刷新
        return new InterceptorStatusToken(origCtx, true, attributes, object);
    }
    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}

4.4, вернемся к тому, с чего началась наша мечта: метод FilterSecurityInterceptor.invoke()

	public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			// 过滤器已应用于此请求,用户希望我们观察每个请求处理一次,因此不要重新进行安全检查
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// 第一次调用这个请求,所以执行安全检查
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
		//返回至此处  
        //InterceptorStatusToken类上的doc注释说:
        //AbstractSecurityInterceptor子类接收的返回对象。
		//这个类反映了安全拦截的状态,以便最终调用AbstractSecurityInterceptor.afterInvocation(InterceptorStatusToken, Object)可以正确整理。
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
            //每个过滤器的必备代码
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		}
		finally {
			super.finallyInvocation(token);
		}
		super.afterInvocation(token, null);
	}

Четвертая, вторая половина

Аутентификация аннотаций методов на самом деле представляет собой пошаговое рассмотрение того, как она выполняется.

image-20211016140515085

По умолчанию эта картинка понятна всем, давайте перейдем непосредственно к MethodSecurityInterceptor, чтобы посмотреть, что он делает.

4.1. Запись: MethodSecurityInterceptor

//提供对基于 AOP 联盟的方法调用的安全拦截。
//此安全拦截器所需的SecurityMetadataSource是MethodSecurityMetadataSource类型。 这与基于 AspectJ 的安全拦截器 ( AspectJSecurityInterceptor ) 共享,因为两者都与 Java Method 。
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {

	private MethodSecurityMetadataSource securityMetadataSource;

	//此方法应用于对MethodInvocation强制实施安全性。
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
        //beforeInvocation 这个有没有似曾相识 ,莫错哈  就是我们之前在 FilterSecurityInterceptor 看到的那个 
        //需要注意到的是 之前我们传的参是一个  FilterInvocation ,这里则是一个  MethodInvocation 。
		InterceptorStatusToken token = super.beforeInvocation(mi);
		Object result;
		try {
			result = mi.proceed();
		}
		finally {
			super.finallyInvocation(token);
		}
		return super.afterInvocation(token, result);
	}
    //...
}

MethodInvocation Комментарий :doc представляет собой «Описание вызова метода, предоставленное перехватчику во время вызова метода. Вызов метода является точкой соединения и может быть перехвачен перехватчиком метода».

4.2. Введите AbstractSecurityInterceptor

Проверка авторизации методом beforeInvocation()

image-20211016171816409

Кроме того, значения, полученные отладкой, здесь тоже разные, о чем я только что сказал выше.

Получить политику доступа к ресурсам:FilterSecurityInterceptor Разрешение Collection, необходимое для доступа к текущему ресурсу, получается из подкласса DefaultFilterInvocationSecurityMetadataSource SecurityMetadataSource. SecurityMetadataSource на самом деле является абстракцией политики доступа для чтения, а содержимое для чтения — это на самом деле настроенные нами правила доступа.

protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/r/r1").hasAuthority("r1")
        .antMatchers("/r/r2").hasAuthority("r2")
        ....
}

image-20211016182852123

Процесс в середине похож на первую половину, так что много говорить не буду. Давайте посмотрим непосредственно на AffirmativeBased.

4.3. Перейдите к: AffirmativeBasedl;

attemptAuthorization(object, attributes, authenticated);
this.accessDecisionManager.decide(authenticated, object, attributes);

Спускаясь вниз, здесь немного отличается от предыдущего, мы использовали WebExpressionVoter раньше, здесь мы используем: PreInvocationAuthorizationAdviceVoter

image-20211015160437849

Затем мы вводим: PreInvocationAuthorizationAdviceVoter, комментарии документа к его классу следующие:

Voter использует реализацию PreInvocationAuthorizationAdvice, сгенерированную из аннотаций @PreFilter и @PreAuthorize, для выполнения действия. На практике, если эти аннотации используются, они обычно содержат всю необходимую логику управления доступом, поэтому система на основе избирателя на самом деле не нужна, достаточно одного AccessDecisionManager, содержащего ту же логику. Однако этот класс легко адаптируется к традиционной реализации AccessDecisionManager на основе избирателя, используемой Spring Security.

Мы легко можем видеть, что это класс, аннотированный в методе обработки. Затем посмотрите на его исходный код.

public class PreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter<MethodInvocation> {

	private final PreInvocationAuthorizationAdvice preAdvice;

	public PreInvocationAuthorizationAdviceVoter(PreInvocationAuthorizationAdvice pre) {
		this.preAdvice = pre;
	}
    //...一些检查方法
	@Override
	public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {
		// 查找 prefilter 和 preauth(或组合)属性,如果两者都为 null,则弃权使用它们调用建议
		PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
		if (preAttr == null) {
			// 没有基于表达式的元数据,所以弃权
			return ACCESS_ABSTAIN;
		}
        //在这里又委托给 PreInvocationAuthorizationAdvice接口的before方法来做判断
		return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;
	}

	private PreInvocationAttribute findPreInvocationAttribute(Collection<ConfigAttribute> config) {
		for (ConfigAttribute attribute : config) {
			if (attribute instanceof PreInvocationAttribute) {
				return (PreInvocationAttribute) attribute;
			}
		}
		return null;
	}
}

Взгляните на реализацию по умолчанию метода before интерфейса PreInvocationAuthorizationAdvice:

Описание метода «до»: совет «до», который следует выполнить для выполнения любой необходимой фильтрации и принятия решения о том, разрешен ли вызов метода.

image-20211015161226479

Сначала поговорим о его параметрах:(Authentication authentication,MethodInvocation mi,PreInvocationAttribute attr), Первый — это пользователь, вошедший в систему в данный момент, второй — метод, который нужно выполнить, а третий — аннотация к методу. Мы можем легко увидеть смысл этого кода, который заключается в том, чтобы сравнить, есть ли у вошедшего в систему пользователя необходимые разрешения для этого метода.

Также кратко объясните:

  1. Аннотация dco для createEvaluationContext: предоставляет контекст оценки, в котором оцениваются безопасные выражения (т. е. выражения SpEL) вызывающего типа. Я лично не особо углублялся в это, поэтому я не могу объяснить это ясно, вы можете проверить это.

  2. Кроме того, давайте взглянем на подробную информацию об отладке, и все должны быть в состоянии понять это.

    image-20211016182251432

Следующий шаг - шаг за шагом возвращаться

И наконец:

image-20211015162556081

Результат здесь — это возвращаемый результат выполнения метода. Следующий шаг — шаг за шагом вернуться к цепочке фильтров.

ибо здесьproceed Метод больше не является глубинным. Выдёргивая этот момент, я боюсь, что смогу написать сразу целую статью.

Существует множество внутренних динамических агентов и отражений, которые связаны слой за слоем, но не являются предметом нашего исследования. Иди, иди, иди.

V. Резюме

Эта картинка была найдена на Baidu, и общий процесс на самом деле такой.

20200503214152166

На самом деле внутри есть много-много вещей, заслуживающих пристального внимания, о которых нельзя написать в этой простой статье.

6. Самоощущение

Я до сих пор помню, как первый раз сказал посмотреть исходники, когда готовился к изучению Mybatis. Тогда я смотрел его несколько дней. Больше не смог прочитать. Не нашел подходящего метода. .Хотелось увидеть все.., без очень конкретной цели, приводящее к сплошным разочарованиям, а результат - не более того.

Второй реальный смысл смотреть на исходный код — это смотреть на Security . Причина в том, что когда я писал проект, мой фронтенд-друг сказал, что на большинстве веб-сайтов теперь есть несколько способов входа в систему, вы можете это сделать?

Мужчина не должен говорить нет, и тогда я согласилась. В результате везде сумасшедшие Baidu, Google и блоги. Интернет настолько огромен, конечно же, есть много примеров и анализа исходного кода. Но статьи, которые я нашел, либо содержали только основной код, либо были неподходящими (большими и трудными для извлечения), и, короче говоря, предложение не могло состояться. Это раздражает.

Однако в статье упоминается, что необходимо понять процесс входа в систему безопасности, а затем скопировать его, что обычно называется копированием домашней работы. В конце концов, это действительно пошаговая отладка, чтобы увидеть процесс входа в систему безопасности, и я написал первую статью.Анализ процесса аутентификации входа в систему безопасности, а затем используйтеSpring Security реализует несколько методов входа в систему, таких как код подтверждения электронной почты, вход по номеру телефона.. В этот раз это было совпадение, и это тоже была мысль, и на написание этой статьи ушло очень много времени. Он по-прежнему чувствует себя очень хорошо.

Я надеюсь, вам понравится.Если xdm также заинтересован в этом, я надеюсь, что вы будете отлаживать несколько раз, когда у вас будет время, и ваша память будет намного глубже. и на самом делеНа бумаге в конце концов я чувствую себя мелким, и я абсолютно точно знаю, что это дело должно быть сделано..

Статьи по Теме:

  1. SpringBoot интегрирует SpringSecurity в качестве фреймворка безопасности
  2. Подробное объяснение процесса входа в систему безопасности
  3. Безопасность реализует несколько методов входа в систему, код подтверждения электронной почты и вход в систему с кодом подтверждения мобильного телефона.
  4. Разрешения SpringSecurity с именем ROLE_Problem

Это все для сегодняшней статьи.

привет я блоггер宁在春:Домой

Если у вас есть какие-либо сомнения в статье, оставьте сообщение или личное сообщение или добавьте контактную информацию на главную страницу, и мы ответим как можно скорее.

Если вы обнаружите какие-либо проблемы в статье, надеюсь, вы меня поправите, большое спасибо.

Если вы считаете это полезным, пожалуйста, ставьте палец вверх и уходите!