Вопрос скорее архитектурный, чем связан с языком. Будет ли моя реализация (в TODO описал) соответствовать протоколу oAuth? Приложение получается все-таки будет иметь уязвимость длинною в срок жизни access token? PHP: /** * I used architecture for token request and response from here: * ======================================= * https://auth0.com/learn/refresh-tokens/ * ======================================= * * |Token | * |---------------------| * |INT id | * |VARCHAR appName | * |VARCHAR refreshToken | * |BIGINT expiration | */ @RequestMapping(value = "/token", method = RequestMethod.POST) public ResponseEntity<?> register(@RequestBody TokenRequest tokenRequest) throws Exception { TokenResponse tokenResponse = null; final User user; if (tokenRequest.getGrant_type().equals("password")) { user = userService.findOne(tokenRequest.getUsername()); String rawPassword = tokenRequest.getPassword(); String encodedPassword = user.getPassword(); if (!encoder.matches(rawPassword, encodedPassword)) { throw new Exception(getClass().getCanonicalName() + " Invalid credentials!"); } String token = jwtTokenUtil.generateToken(user); /** * TODO * * 1. Generate access token (JWT) * 2. Generate refresh token (random value, 14 characters) * 3. Persist refresh token and its expiration time to the database * 4. Return access token with refresh token and expiration time of access token * Client actually doesn't have to know expiration time of refresh token */ } if (tokenRequest.getGrant_type().equals("refresh_token")) { // TODO Update token if refresh token is valid /** * TODO * * 1. Find refresh token in database * 2. Check its expiration time * :: Continue if refresh token is not expired * 3. Generate new refresh token and update record in database with new token and expiration time * 4. Generate new access token * 5. Return access token with refresh token and expiration time of access token * Client actually doesn't have to know expiration time of refresh token */ } return ResponseEntity.ok(tokenResponse); }
Короче, кажется понял. Утро вечера мудренее. Стандартная аутентификация и авторизация: Пользователь отправляет username и password POST запросом на сервер Сервер создает у себя файл с рандомным названием Записывает в этот файл в формате "key=value" username и password пользователя, а также время жизни, например 5 минут Сервер возвращает клиенту ответ c header-ом "Set-Cookie: SESSIONID=<название_файла>" Далее клиент, к примеру, хочет обратиться к /profile странице, доступ к которой есть только у авторизованных пользователей Клиент посылает запрос POST /profile HTTP/1.1, браузер автоматически добавит header "Cookie: SESSIONID=<название файла>" На сервере мы проверяем, что есть header "Cookie: ..." в запросе, находим SESSIONID Находим файл с названием равным SESSIONID, нашли ... значит пользователь авторизован, если нет, тогда ошибку вернуть. P.S. Файл сессии создается только при успешной аутентификации oAuth 2.0 Клиент обращается к серверу: {"username":"admin","password":"123","grant_type":"password"} Сервер проверяет соответствие username и password, с теми, что в базе данных Если, все ОК, то создаем Json Web Token (access token), в payload котором будет username, роли, его id и дата expiration-а (через 1 час) Генерируем рандомный refresh_token, у которого будет срок жизни - месяц Вставляем refresh_token, дату expiration, appName (название сервиса, в данном случае GRANT_ALL, т.к. с того же домена запрос) в бд Возвращаем это все {"access_token":"xxx", "refresh_token":"xxx", "expiration_date":"213442..."} клиенту (expiration дату access токена) Angular записывает access_token, refresh_token и дату expiration в localStorage или куки. Перед каждым запросом к серверу 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 написано "может быть". Поправьте меня, если в чем-то не прав.
господь тя храни, чувак, ты идёшь по дороге с тысячей ям и ловушек а ты эта, че не заюзал ченить готовое-то?
Согласен. Конечно на реальном проекте 100% юзать готовые решения, например Spring Boot oAuth2. Но я ж студент, хочу свой велосипед, чтоб понимать лучше.
а, не, в академических рамках это всё просто дрочи пока не заработает просто на практике, как выяснилось, все этот oAuth понимаю по-разному и реализуют по-всякому. т.е. вроде протокол один, а для разных сайтов приходится пилить отдельные изменения в коде. а так, да - исследуй и нам расскажи.