Рубрики
Без рубрики

Уведомить Пользователя о Входе с Нового устройства или Местоположения

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

Автор оригинала: Daniel Barrigas.

1. введение

В этом уроке мы продемонстрируем, как мы можем проверить | если наши | пользователи /////////////////////////////////////////////////////////////////

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

2. Местоположение пользователей и сведения об устройстве

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

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

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

2.1. Расположение Устройства

Прежде чем мы сможем оценить местоположение наших пользователей, нам необходимо получить их исходный IP-адрес.

Мы можем сделать это с помощью:

  • X-Forwarded-For – стандартный заголовок де-факто для идентификации исходного IP-адреса клиента, подключающегося к веб-серверу через HTTP-прокси или балансировщик нагрузки
  • ServletRequest.getRemoteAddr() – служебный метод, возвращающий исходный IP-адрес клиента или последнего прокси-сервера, отправившего запрос

Извлечение IP-адреса пользователя из HTTP-запроса не совсем надежно, так как они могут быть подделаны. Однако давайте упростим это в нашем учебнике и предположим, что это не так.

Как только мы получим IP-адрес, мы сможем преобразовать его в реальное местоположение с помощью геолокации .

2.2. Сведения об Устройстве

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

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

Вот пример того, как это может выглядеть:

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 
  (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36

В нашем примере выше устройство работает на Mac OS |/X 10.14 и используется Chrome 71.0 чтобы отправить запрос.

Вместо того, чтобы внедрять User-Agent parser с нуля, мы собираемся прибегнуть к существующим решениям, которые уже были протестированы и являются более надежными.

3. Обнаружение нового устройства или местоположения

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

public class MySimpleUrlAuthenticationSuccessHandler 
  implements AuthenticationSuccessHandler {
    //...
    @Override
    public void onAuthenticationSuccess(
      final HttpServletRequest request,
      final HttpServletResponse response,
      final Authentication authentication)
      throws IOException {
        handle(request, response, authentication);
        //...
        loginNotification(authentication, request);
    }

    private void loginNotification(Authentication authentication, 
      HttpServletRequest request) {
        try {
            if (authentication.getPrincipal() instanceof User) { 
                deviceService.verifyDevice(((User)authentication.getPrincipal()), request); 
            }
        } catch(Exception e) {
            logger.error("An error occurred verifying device or location");
            throw new RuntimeException(e);
        }
    }
    //...
}

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

Однако, прежде чем мы перейдем к нашему DeviceService , давайте создадим нашу DeviceMetadata сущность для сохранения данных наших пользователей с течением времени:

@Entity
public class DeviceMetadata {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Long userId;
    private String deviceDetails;
    private String location;
    private Date lastLoggedIn;
    //...
}

И его Репозиторий :

public interface DeviceMetadataRepository extends JpaRepository {
    List findByUserId(Long userId);
}

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

4. Извлечение Местоположения Нашего Пользователя

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

private String extractIp(HttpServletRequest request) {
    String clientIp;
    String clientXForwardedForIp = request
      .getHeader("x-forwarded-for");
    if (nonNull(clientXForwardedForIp)) {
        clientIp = parseXForwardedHeader(clientXForwardedForIp);
    } else {
        clientIp = request.getRemoteAddr();
    }
    return clientIp;
}

Если в запросе есть заголовок X-Forwarded-For , мы будем использовать его для извлечения их IP-адреса; в противном случае мы будем использовать метод getRemoteAddr () .

Как только у нас будет их IP-адрес, мы сможем оценить их местоположение с помощью Maxmind:

private String getIpLocation(String ip) {
    String location = UNKNOWN;
    InetAddress ipAddress = InetAddress.getByName(ip);
    CityResponse cityResponse = databaseReader
      .city(ipAddress);
        
    if (Objects.nonNull(cityResponse) &&
      Objects.nonNull(cityResponse.getCity()) &&
      !Strings.isNullOrEmpty(cityResponse.getCity().getName())) {
        location = cityResponse.getCity().getName();
    }    
    return location;
}

5. Сведения об устройстве Пользователя

Поскольку заголовок User-Agent содержит всю необходимую нам информацию, остается только извлечь ее. Как мы уже упоминали ранее, с помощью User-Agent parser (в данном случае uap-java ) получение этой информации становится довольно простым:

private String getDeviceDetails(String userAgent) {
    String deviceDetails = UNKNOWN;
    
    Client client = parser.parse(userAgent);
    if (Objects.nonNull(client)) {
        deviceDetails = client.userAgent.family
          + " " + client.userAgent.major + "." 
          + client.userAgent.minor + " - "
          + client.os.family + " " + client.os.major
          + "." + client.os.minor; 
    }
    return deviceDetails;
}

6. Отправка уведомления о входе в систему

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

Давайте взглянем на наш DeviceService. проверка Устройство() метод:

public void verifyDevice(User user, HttpServletRequest request) {
    
    String ip = extractIp(request);
    String location = getIpLocation(ip);

    String deviceDetails = getDeviceDetails(request.getHeader("user-agent"));
        
    DeviceMetadata existingDevice
      = findExistingDevice(user.getId(), deviceDetails, location);
        
    if (Objects.isNull(existingDevice)) {
        unknownDeviceNotification(deviceDetails, location,
          ip, user.getEmail(), request.getLocale());

        DeviceMetadata deviceMetadata = new DeviceMetadata();
        deviceMetadata.setUserId(user.getId());
        deviceMetadata.setLocation(location);
        deviceMetadata.setDeviceDetails(deviceDetails);
        deviceMetadata.setLastLoggedIn(new Date());
        deviceMetadataRepository.save(deviceMetadata);
    } else {
        existingDevice.setLastLoggedIn(new Date());
        deviceMetadataRepository.save(existingDevice);
    }
}

После извлечения информации мы сравниваем ее с существующими Метаданными устройства записями, чтобы проверить, есть ли запись, содержащая ту же информацию:

private DeviceMetadata findExistingDevice(
  Long userId, String deviceDetails, String location) {
    List knownDevices
      = deviceMetadataRepository.findByUserId(userId);
    
    for (DeviceMetadata existingDevice : knownDevices) {
        if (existingDevice.getDeviceDetails().equals(deviceDetails) 
          && existingDevice.getLocation().equals(location)) {
            return existingDevice;
        }
    }
    return null;
}

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

В противном случае мы просто обновим атрибут last Logged In знакомого устройства.

7. Заключение

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

Полную реализацию этого руководства можно найти на Github .