Почему вы хотите пройти процесс входа в систему 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) Сначала получите authentication
Class, текущего поставщика, чтобы определить, поддерживать ли аутентификацию.
(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
.