Статья проведет вас через процесс входа в Spring Security.

Почему вы хотите пройти процесс входа в систему Spring Security со всеми? Это из-за вопроса, который задают мои друзья: как динамически изменять информацию о пользователе в Spring Security?

Если вы разобрались с процессом входа в систему Spring Security, на самом деле это не проблема.

Для начала кратко опишем сценарий проблемы:

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

1. Повсеместная аутентификация

Друзья, которые играли в Spring Security, знают, что в Spring Security есть очень важный объект, называемый аутентификацией. Мы можем внедрить аутентификацию где угодно, чтобы получить информацию о текущем вошедшем в систему пользователе. Аутентификация сама по себе является интерфейсом и имеет множество классов реализации:

Вставьте описание изображения сюда
Вставьте описание изображения сюда
В этой замечательной реализации мы чаще всего используем класс, UsernamePasswordAuthenticationTokenно когда мы открыли исходный код этого класса, но обнаружили, что этот класс посредственен, у него было только два свойства, два конструктора, а также ряд get/setметодов; конечно, У него больше атрибутов в родительском классе.

Но только по двум его атрибутам мы можем приблизительно увидеть, что этот класс сохраняет основную информацию о нашем вошедшем в систему пользователю. Итак, как наша информация для входа в систему хранится в этих двух объектах? Это поможет разобраться с процессом входа в систему.

Два, процесс входа в систему

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

publicclass UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
    
    
	public UsernamePasswordAuthenticationFilter() {
    
    
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
    
    
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		setDetails(request, authRequest);
		returnthis.getAuthenticationManager().authenticate(authRequest);
	}
	protected String obtainPassword(HttpServletRequest request) {
    
    
		return request.getParameter(passwordParameter);
	}
	protected String obtainUsername(HttpServletRequest request) {
    
    
		return request.getParameter(usernameParameter);
	}
	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
    
    
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}
}

Согласно этому исходному коду мы можем увидеть:

(1) с помощью методов first obtainUsernameи obtainPasswordизвлечения внутри запроса имени пользователя / пароля, метод извлечения - request.getParameter, поэтому по умолчанию Spring Security формирует параметры входа в систему, которые должны передаваться в форме ключа / значения, и не может передавать параметры JSON , Если вы хотите передать параметры JSON, просто измените логику здесь.

(2) После получения запроса был передан имя пользователя / пароль, то на конфигурации UsernamePasswordAuthenticationTokenобъекта передается usernameи password, , usernameсоответствуют UsernamePasswordAuthenticationTokenпо principalатрибутам, и passwordимеет соответствующие его credentialsатрибутов.

(3) Далее, setDetailsметод для detailsприсвоения атрибута UsernamePasswordAuthenticationTokenсам по себе не является detailsсвойством, это свойство в родительском классе AbstractAuthenticationTokenв. detailsЭто объект, который помещается внутри WebAuthenticationDetailsэкземпляра двух описываемых в основном информации, запроса remoteAddressи запроса sessionId.
(4) Последний шаг - вызвать метод аутентификации для проверки.

Что ж, из этого исходного кода видно, что вся запрошенная информация в основном нашла свое собственное местоположение, и нам будет удобно получить ее в будущем.

Затем давайте посмотрим на конкретную запрошенную операцию проверки.

В предыдущем attemptAuthenticationметоде, заключительный этап процесса начал проверять, операция проверки начинается , чтобы получить AuthenticationManager, получить здесь является то , что ProviderManager, таким образом , то мы входим ProviderManagerв authenticateпроцесс, конечно же , этот метод является сравнительно долго, я Вот лишь несколько важных моментов:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	Class<? extends Authentication> toTest = authentication.getClass();
	for (AuthenticationProvider provider : getProviders()) {
    
    
		if (!provider.supports(toTest)) {
    
    
			continue;
		}
		result = provider.authenticate(authentication);
		if (result != null) {
    
    
			copyDetails(authentication, result);
			break;
		}
	}
	if (result == null && parent != null) {
    
    
		result = parentResult = parent.authenticate(authentication);
	}
	if (result != null) {
    
    
		if (eraseCredentialsAfterAuthentication
				&& (result instanceof CredentialsContainer)) {
    
    
			((CredentialsContainer) result).eraseCredentials();
		}
		if (parentResult == null) {
    
    
			eventPublisher.publishAuthenticationSuccess(result);
		}
		return result;
	}
	throw lastException;
}

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

(1) Сначала получите authenticationClass, текущего поставщика, чтобы определить, поддерживать ли аутентификацию.

(2) Если он поддерживается, вызовите метод аутентификации поставщика, чтобы начать проверку. После завершения проверки будет возвращена новая проверка подлинности. Я скоро обсудю с вами конкретную логику этого метода.

(3) поставщик, где может быть больше, если поставщик не может аутентифицировать метод, возвращает нормальный Authenticationродительский элемент поставщика вызывающего authenticateметода для продолжения проверки.

(4) Метод copyDetails используется для копирования свойства сведений старого токена в новый токен.

(5) Затем будет вызван метод eraseCredentials, чтобы стереть учетную информацию, которая является вашим паролем.Этот метод стирания относительно прост, он заключается в очистке атрибута учетных данных в токене.

(6) Наконец, publishAuthenticationSuccessметод будет транслировать событие успешного входа в систему.
Общий процесс аналогичен описанному выше. В цикле for поставщик, который вы получаете впервые, является AnonymousAuthenticationProvider. Этот поставщик вообще не поддерживает UsernamePasswordAuthenticationToken, то есть возвращает false непосредственно в методе providerr.supports для завершения цикла for, и Он войдет в следующий if и напрямую вызовет родительский метод аутентификации для проверки.

Родитель ProviderManager, он снова вернется к этому authenticateметоду. Снова authenticateметод back , стал поставщиком DaoAuthenticationProvider, поставщик поддерживается UsernamePasswordAuthenticationToken, он будет хорошо работать в authenticateметоде класса и DaoAuthenticationProviderунаследован от AbstractUserDetailsAuthenticationProviderметода аутентификации, а не переопределяет его, поэтому мы, наконец, пришли AbstractUserDetailsAuthenticationProvider#authenticateк подходу:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
			: authentication.getName();
	user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
	preAuthenticationChecks.check(user);
	additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
	postAuthenticationChecks.check(user);
	Object principalToReturn = user;
	if (forcePrincipalAsString) {
    
    
		principalToReturn = user.getUsername();
	}
	return createSuccessAuthentication(principalToReturn, authentication, user);
}

Логика здесь относительно проста:

(1) Сначала извлеките имя пользователя для входа из Authentication.

(2) затем выполняется usernameвызов retrieveUserметода для получения текущего пользовательского объекта, этот шаг вызовет наш собственный во время входа в систему для записи loadUserByUsernameметода, поэтому на самом деле происходит возврат к пользовательским объектам, в которые вы входите, вы можете обратиться к микроперсоналу org / javaboy /vhr/service/HrService.java#L34.

(3) Затем вызовите preAuthenticationChecks.checkметод для проверки свойств каждой учетной записи пользователя в нормальном состоянии, например, отключена ли учетная запись, заблокирована ли учетная запись, срок действия учетной записи истек и т. Д.

(4) additionalAuthenticationChecksМетод заключается в сравнении паролей. Многим друзьям интересно, как зашифрованы пароли Spring Security и как их сравнивать. Вы можете понять это здесь. Поскольку логика сравнения очень проста, я не буду публиковать здесь код. .

(5) Наконец, postAuthenticationChecks.checkпроверьте, не истек ли срок действия пароля.

(6) Тогда есть forcePrincipalAsStringсвойство, является ли заставить Authenticationв principalсобственность строки, мы начинаем в этом отеле UsernamePasswordAuthenticationFilterфактически установлен в строке (т.е. имя пользователя) класс, но по умолчанию, когда пользователь входит После успеха значение этого атрибута становится объектом текущего пользователя. Причина этого в том, что forcePrincipalAsStringзначение по умолчанию - false, но не меняйте этот факт, используйте false, чтобы получить информацию о текущем пользователе во второй половине времени, но намного проще.

(7) Наконец, createSuccessAuthenticationпостроив новый метод UsernamePasswordAuthenticationToken.

Итак, процесс проверки входа в систему теперь в основном повторяется для всех. Тогда возникает еще один вопрос, где мы ищем информацию о авторизованном пользователе?

Три, сохранение пользовательской информации

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

Мы перешли к UsernamePasswordAuthenticationFilterродительскому классу AbstractAuthenticationProcessingFilter, этот класс мы часто видим, потому что много раз, когда мы хотим, чтобы пользовательский код Spring Security или параметры входа в систему аутентификации были изменены на JSON, и всем нам нужны настраиваемые фильтры, унаследованные от AbstractAuthenticationProcessingFilterнесомненно, UsernamePasswordAuthenticationFilter#attemptAuthenticationименно к AbstractAuthenticationProcessingFilterклассу doFilterсрабатывает метод:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
    
    
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	Authentication authResult;
	try {
    
    
		authResult = attemptAuthentication(request, response);
		if (authResult == null) {
    
    
			return;
		}
		sessionStrategy.onAuthentication(authResult, request, response);
	}
	catch (InternalAuthenticationServiceException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	catch (AuthenticationException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	if (continueChainBeforeSuccessfulAuthentication) {
    
    
		chain.doFilter(request, response);
	}
	successfulAuthentication(request, response, chain, authResult);
}

Из приведенного выше кода мы видим, что когда attemptAuthenticationметод вызывается, на самом деле, триггер UsernamePasswordAuthenticationFilter#attemptAuthenticationметода выдает исключение при входе в систему, unsuccessfulAuthenticationвызывается метод, а при успешном входе в систему вызывается successfulAuthenticationметод, затем мы рассмотрим successfulAuthenticationметоды:

protected void successfulAuthentication(HttpServletRequest request,
		HttpServletResponse response, FilterChain chain, Authentication authResult)
		throws IOException, ServletException {
    
    
	SecurityContextHolder.getContext().setAuthentication(authResult);
	rememberMeServices.loginSuccess(request, response, authResult);
	// Fire event
	if (this.eventPublisher != null) {
    
    
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
	successHandler.onAuthenticationSuccess(request, response, authResult);
}

Здесь есть очень важный код, то есть SecurityContextHolder.getContext().setAuthentication(authResult);информация о пользователе хранится в логине, здесь успешно, то есть в любом месте, если мы хотим получить информацию для входа в систему, доступны из входа SecurityContextHolder.getContext(), вы хотите изменить, также может быть в Измените здесь.

Наконец, все также видели один successHandler.onAuthenticationSuccess. Это метод обратного вызова, который мы настраиваем в SecurityConfig. Он запускается здесь. Вы также можете обратиться к конфигурации в микроперсонале org/javaboy/vhr/config/SecurityConfig.

рекомендация

отblog.csdn.net/nanhuaibeian/article/details/108585792