За последние 24 часа нас посетили 20008 программистов и 1402 робота. Сейчас ищут 993 программиста ...

oAuth 2.0 Правильно ли сделал план?

Тема в разделе "PHP для новичков", создана пользователем виталий032, 27 июн 2018.

  1. виталий032

    виталий032 Активный пользователь

    С нами с:
    31 янв 2014
    Сообщения:
    227
    Симпатии:
    30
    Адрес:
    Владивосток
    Вопрос скорее архитектурный, чем связан с языком.

    Будет ли моя реализация (в TODO описал) соответствовать протоколу oAuth?
    Приложение получается все-таки будет иметь уязвимость длинною в срок жизни access token?

    PHP:
    1. /**
    2.      * I used architecture for token request and response from here:
    3.      * =======================================
    4.      * https://auth0.com/learn/refresh-tokens/
    5.      * =======================================
    6.      *
    7.      * |Token                |
    8.      * |---------------------|
    9.      * |INT id               |
    10.      * |VARCHAR appName         |
    11.      * |VARCHAR refreshToken |
    12.      * |BIGINT  expiration   |
    13.      */
    14.     @RequestMapping(value = "/token", method = RequestMethod.POST)
    15.     public ResponseEntity<?> register(@RequestBody TokenRequest tokenRequest) throws Exception {
    16.         TokenResponse tokenResponse = null;
    17.         final User user;
    18.      
    19.         if (tokenRequest.getGrant_type().equals("password")) {
    20.             user = userService.findOne(tokenRequest.getUsername());
    21.          
    22.             String rawPassword = tokenRequest.getPassword();
    23.             String encodedPassword = user.getPassword();
    24.          
    25.             if (!encoder.matches(rawPassword, encodedPassword)) {
    26.                 throw new Exception(getClass().getCanonicalName() + " Invalid credentials!");
    27.             }
    28.          
    29.             String token = jwtTokenUtil.generateToken(user);
    30.          
    31.             /**
    32.              * TODO
    33.              *
    34.              * 1. Generate access token (JWT)
    35.              * 2. Generate refresh token (random value, 14 characters)
    36.              * 3. Persist refresh token and its expiration time to the database
    37.              * 4. Return access token with refresh token and expiration time of access token
    38.              *    Client actually doesn't have to know expiration time of refresh token
    39.              */
    40.         }
    41.      
    42.         if (tokenRequest.getGrant_type().equals("refresh_token")) {
    43.             // TODO Update token if refresh token is valid
    44.          
    45.             /**
    46.              * TODO
    47.              *
    48.              * 1. Find refresh token in database
    49.              * 2. Check its expiration time
    50.              * :: Continue if refresh token is not expired
    51.              * 3. Generate new refresh token and update record in database with new token and expiration time
    52.              * 4. Generate new access token
    53.              * 5. Return access token with refresh token and expiration time of access token
    54.              *       Client actually doesn't have to know expiration time of refresh token
    55.              */
    56.         }
    57.      
    58.         return ResponseEntity.ok(tokenResponse);
    59.     }
     
  2. виталий032

    виталий032 Активный пользователь

    С нами с:
    31 янв 2014
    Сообщения:
    227
    Симпатии:
    30
    Адрес:
    Владивосток
    Короче, кажется понял. Утро вечера мудренее.

    Стандартная аутентификация и авторизация:
    1. Пользователь отправляет username и password POST запросом на сервер
    2. Сервер создает у себя файл с рандомным названием
    3. Записывает в этот файл в формате "key=value" username и password пользователя, а также время жизни, например 5 минут
    4. Сервер возвращает клиенту ответ c header-ом "Set-Cookie: SESSIONID=<название_файла>"
    5. Далее клиент, к примеру, хочет обратиться к /profile странице, доступ к которой есть только у авторизованных пользователей
    6. Клиент посылает запрос POST /profile HTTP/1.1, браузер автоматически добавит header "Cookie: SESSIONID=<название файла>"
    7. На сервере мы проверяем, что есть header "Cookie: ..." в запросе, находим SESSIONID
    8. Находим файл с названием равным SESSIONID, нашли ... значит пользователь авторизован, если нет, тогда ошибку вернуть.
    P.S. Файл сессии создается только при успешной аутентификации

    oAuth 2.0
    1. Клиент обращается к серверу: {"username":"admin","password":"123","grant_type":"password"}
    2. Сервер проверяет соответствие username и password, с теми, что в базе данных
    3. Если, все ОК, то создаем Json Web Token (access token), в payload котором будет username, роли, его id и дата expiration-а (через 1 час)
    4. Генерируем рандомный refresh_token, у которого будет срок жизни - месяц
    5. Вставляем refresh_token, дату expiration, appName (название сервиса, в данном случае GRANT_ALL, т.к. с того же домена запрос) в бд
    6. Возвращаем это все {"access_token":"xxx", "refresh_token":"xxx", "expiration_date":"213442..."} клиенту (expiration дату access токена)
    7. Angular записывает access_token, refresh_token и дату expiration в localStorage или куки.
    8. Перед каждым запросом к серверу angular проверяет не протух ли access_token
    • Если не протух, то добавит header "Authorization: Bearer <access_token>" и отправит
    • Если протух, то отправит запрос на генерацию нового access_token {"refresh_token":"xxx","grant_type":"refresh_token"}
    9.1 Сервер генерирует новую пару access_token и refresh_token, если refresh_token в
    запросе и в бд совпадают. Пароля не нужно.​
    9.2 Если refresh_token в запросе и в бд не совпадают, значит возвращаем ошибку, клиент
    перенаправляет юзера на страницу ввода логина и пароля.​

    • Предположим "хуцкер" узнал access_token: у него будет доступ до того момента, когда истечет этот токен
    • Предположим "хуцкер" узнал и access_token, и refresh_token: когда access_token протухнет, хуцкер сгенерирует новую пару access_token и refresh_token. Когда у жертвы протухнет access_token, то она попытается обновить его, но хуцкер уже обновил refresh_token в бд, поэтому refresh_token жертвы и в бд совпадать не будут. Жертву перебросит на страницу авторизации и она сгенерирует новую пару путем ввода логина и пароля, а хуцкер пойдет уроки делать.
    В чем преимущество oAuth?
    • access_token это JWT, поэтому в него можно положить любую инфу, имя, фамилию, пол, ид, права (по умолчанию кодируется payload, но не шифруется)
    • зоопарку сервисов не надо обращаться к бд, чтобы проверить права и данные юзера (наверное меньше проблем для другой команды разработчиков)
    • разные сервисы имеют ограниченный доступ (мобильная приложение не сможет увидеть сколько у вас осталось на балансе)
    • мы не даем пароль сторонним сервисам, а только access_token с определенными правами, которые можно отозвать удалив refresh_token для сервиса (как наподобие в VK)
    В идеале там вроде должен быть отдельный сервер авторизации и предоставления доступа к API, но на wiki написано "может быть".
    Поправьте меня, если в чем-то не прав.
     
  3. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    господь тя храни, чувак, ты идёшь по дороге с тысячей ям и ловушек

    а ты эта, че не заюзал ченить готовое-то?
     
  4. виталий032

    виталий032 Активный пользователь

    С нами с:
    31 янв 2014
    Сообщения:
    227
    Симпатии:
    30
    Адрес:
    Владивосток
    Согласен. Конечно на реальном проекте 100% юзать готовые решения, например Spring Boot oAuth2. Но я ж студент, хочу свой велосипед, чтоб понимать лучше.
     
  5. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    а, не, в академических рамках это всё просто
    дрочи пока не заработает

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

    а так, да - исследуй и нам расскажи.