В статье вы найдете SpringBoot и SpringSecurity для достижения функции автоматического входа в систему.

Автоматический вход в систему - очень распространенная функция при разработке программного обеспечения. Например, мы авторизуемся в почтовом ящике QQ:

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

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

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

1. Присоединяйтесь к "Помни меня"

Для удобства настройки просто добавьте две зависимости:

Вставьте описание изображения сюда
Добавьте следующий код в класс конфигурации:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    PasswordEncoder passwordEncoder(){
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.inMemoryAuthentication()
                .withUser("yolo")
                .password("123").roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                .and()
                .csrf().disable();
    }
}

Мы видим, что здесь просто добавьте .rememberMe()банку, функция автоматического входа добавлена, успешно введена.

Далее мы случайным образом добавляем тестовый интерфейс:

@RestController
public class HelloController {
    
    
    @GetMapping("/hello")
    public String hello(){
    
    
        return "Hello Yolo !!!";
    }
}

Вставьте описание изображения сюда
В это время все обнаружили, что на странице входа по умолчанию есть еще один вариант - запомнить меня. Мы вводим имя пользователя и пароль, устанавливаем флажок «Запомнить меня», а затем нажимаем кнопку входа в систему, чтобы выполнить операцию входа в систему.

Как вы можете видеть, в данных для входа в систему, помимо имени пользователя и пароля, есть также запомнить меня. Причина, по которой это отображается, - сообщить всем, если вам нужно настроить страницу входа, как насчет ключа опции RememberMe? написать.

После успешного входа в систему он автоматически перейдет к интерфейсу приветствия. Мы отмечаем, что когда система обращается к интерфейсу приветствия, cookie содержит:

Вставьте описание изображения сюда
Здесь было отмечено еще одно remember-me, которое является реализованным здесь ядром, на этом remember-meя объясню, давайте результаты тестов.

Затем мы закрываем браузер, а затем снова открываем его. В нормальных условиях браузер закрывается и открывается снова. Если нам снова нужно получить доступ к интерфейсу приветствия, нам нужно снова войти в систему. Но в этот раз мы снова заходим в интерфейс hello и обнаруживаем, что можем получить к нему доступ напрямую, не входя снова. Это показывает, что наша конфигурация RememberMe вступила в силу (то есть функция автоматического входа в систему вступит в силу в следующий раз).

2. Принцип анализа

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

Во-первых, давайте проанализируем лишнюю remember-meстроку в файле cookie . Это значение представляет собой транскодированную строку Base64. Мы можем использовать некоторые онлайн-инструменты в Интернете для ее декодирования. Вы можете просто написать две строки кода, чтобы декодировать ее самостоятельно:


    @Test
    void contextLoads() {
    
    
        String s = new String(
                Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
        System.out.println("s = " + s);
    }

Выполните этот код, результат будет следующим:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

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

(1) Первый абзац - это имя пользователя, это не должно подвергаться сомнению.
(2) Второй абзац кажется меткой времени.Проанализировав его с помощью онлайн-инструментов или кода Java, мы обнаружили, что это данные две недели спустя.
(3) В третьем абзаце я не буду продавать его. Это значение, вычисленное с помощью хеш-функции MD5. Его открытый текстовый формат таков,
username + ":" + tokenExpiryTime + ":" + password + ":" + keyчто последний ключ представляет собой значение хеш-соли, которое можно использовать для предотвращения изменения токена.

Узнал cookie remember-meпосле значения, тогда мы должны запомнить мой процесс входа в систему и очень легко угадать.

После закрытия и повторного открытия браузера пользователи переходят к интерфейсу hello, затем переносят cookie remember-meна сервер, после службы, чтобы получить значение, вы можете легко вычислить имя пользователя и дату истечения срока действия, а затем в соответствии с пользователем Пароль пользователя извлекается по имени, затем хеш-значение вычисляется с помощью хеш-функции MD5, а затем вычисленное хеш-значение сравнивается с хеш-значением, переданным браузером, чтобы подтвердить, действителен ли токен.

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

Три, анализ исходного кода

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

Здесь в основном представлены два аспекта: один - это процесс генерации токена «запомнить меня», а другой - процесс его анализа.

1. Создать

Основные генерируемые методы обработки:TokenBasedRememberMeServices#onLoginSuccess:

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
  Authentication successfulAuthentication) {
    
    
 String username = retrieveUserName(successfulAuthentication);
 String password = retrievePassword(successfulAuthentication);
 if (!StringUtils.hasLength(password)) {
    
    
  UserDetails user = getUserDetailsService().loadUserByUsername(username);
  password = user.getPassword();
 }
 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 long expiryTime = System.currentTimeMillis();
 expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
 String signatureValue = makeTokenSignature(expiryTime, username, password);
 setCookie(new String[] {
    
     username, Long.toString(expiryTime), signatureValue },
   tokenLifetime, request, response);
}
protected String makeTokenSignature(long tokenExpiryTime, String username,
  String password) {
    
    
 String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
 MessageDigest digest;
 digest = MessageDigest.getInstance("MD5");
 return new String(Hex.encode(digest.digest(data.getBytes())));
}

(1) Сначала извлеките имя пользователя / пароль из аутентификации для успешного входа в систему.
(2) После успешного входа в систему пароль может быть удален.Поэтому, если пароль не был получен в начале, перезагрузите пользователя из UserDetailsService и получите пароль снова.
(3) Затем получите срок действия токена, который по умолчанию составляет две недели.
(4) Следующий вызываемый makeTokenSignatureметод для вычисления значения хеш-функции, значение хеш-функции фактически вычисляется в соответствии с именем пользователя, а токен является действительным паролем, ключом. Если мы не владеем этим ключом, по умолчанию используются RememberMeConfigurer#getKeyметоды set, его значение - строка UUID.
(5) Наконец, введите имя пользователя, срок действия токена и вычисленное значение хеш-функции в Cookie.

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

Поскольку мы не устанавливаем их собственный ключ, значение ключа по умолчанию представляет собой строку UUID, это вызовет проблему, если сервер будет перезапущен, ключ станет, что приведет к распространению до того, как все remember-meтокены автоматического входа станут недействительными, поэтому, Мы можем указать этот ключ. Способ указания следующий:

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("yolo")
            .and()
            .csrf().disable();
}

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

Это процесс генерации токена "запомнить меня". Что касается того, как добраться до метода onLoginSuccess, здесь можно сделать небольшое напоминание:

AbstractAuthenticationProcessingFilter # doFilter -> AbstractAuthenticationProcessingFilter # successAuthentication -> AbstractRememberMeServices # loginSuccess -> TokenBasedRememberMeServices # onLoginSuccess。

2. Анализ

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

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

Spring Security предоставляет RememberMeAuthenticationFilterклассы , разработанные , чтобы сделать связанные вещи, мы рассмотрим RememberMeAuthenticationFilterв doFilterметоде:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  throws IOException, ServletException {
    
    
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;
 if (SecurityContextHolder.getContext().getAuthentication() == null) {
    
    
  Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
    response);
  if (rememberMeAuth != null) {
    
    
    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
    onSuccessfulAuthentication(request, response, rememberMeAuth);
    if (this.eventPublisher != null) {
    
    
     eventPublisher
       .publishEvent(new InteractiveAuthenticationSuccessEvent(
         SecurityContextHolder.getContext()
           .getAuthentication(), this.getClass()));
    }
    if (successHandler != null) {
    
    
     successHandler.onAuthenticationSuccess(request, response,
       rememberMeAuth);
     return;
    }
   }
  chain.doFilter(request, response);
 }
 else {
    
    
  chain.doFilter(request, response);
 }
}

Этот метод является наиболее важной частью заключается в том, что, если SecurityContextHolderне удается заставить пользователя войти в текущий экземпляр, а затем вызвать rememberMeServices.autoLoginлогику для входа в систему, мы смотрим на этот метод:

public final Authentication autoLogin(HttpServletRequest request,
  HttpServletResponse response) {
    
    
 String rememberMeCookie = extractRememberMeCookie(request);
 if (rememberMeCookie == null) {
    
    
  return null;
 }
 logger.debug("Remember-me cookie detected");
 if (rememberMeCookie.length() == 0) {
    
    
  logger.debug("Cookie was empty");
  cancelCookie(request, response);
  return null;
 }
 UserDetails user = null;
 try {
    
    
  String[] cookieTokens = decodeCookie(rememberMeCookie);
  user = processAutoLoginCookie(cookieTokens, request, response);
  userDetailsChecker.check(user);
  logger.debug("Remember-me cookie accepted");
  return createSuccessfulAuthentication(request, user);
 }
 catch (CookieTheftException cte) {
    
    
  
  throw cte;
 }
 cancelCookie(request, response);
 return null;
}

Вы можете видеть, что это для извлечения cookieинформации, и cookieинформация декодируется, после декодирования, а затем вызывается processAutoLoginCookieметод для проверки, processAutoLoginCookieкодовый подход, который я не буду использовать, основной процесс состоит в том, чтобы сначала получить имя пользователя и дату истечения срока действия, затем имя пользователя Запрашивается пароль пользователя, а затем хеш-значение вычисляется с помощью хеш-функции MD5, а затем полученное хеш-значение сравнивается с хеш-значением, переданным браузером, чтобы подтвердить, действителен ли токен, а затем нужно ли входить в систему. эффективный.

Четыре, резюме

Прочитав приведенную выше статью, возможно, выяснилось, что если мы откроем RememberMeфункцию, самое главное - это вставить cookieтокен, а токен выходит за sessionпределы ограничений, даже если сервер перезагружен, даже если браузер закрыт и снова открыт. , Пока токен не истек, данные доступны.

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

Но на самом деле это парадокс: чтобы улучшить взаимодействие с пользователем (уменьшить количество входов в систему), наша система неизбежно приводит к некоторым проблемам безопасности, но мы можем снизить риски безопасности до минимума с помощью технологий.

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

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