Галоўная / Артыкулы / Аутентыкацыя проты разрэшэння: дзе кожна з іх палягае ў вашам коде

Аутентыкацыя проты разрэшэння: дзе кожна з іх палягае ў вашам коде

Дазвольце даклэ научыцца, як аутентыкацыя і автарызацыя разлічаюцца па способе рэалізацыі, кодах стану HTTP і архітэктуре системы, каб запобiec частым проблемам з безпекай.

1335 слоў

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

Якща вы калі-небудзь адправлялі код 401 у ситуацыі, калі на самай працэ быў код 403, або запускалі процес заходжання і заявлялі, што аутентыкацыя завершана, не чыпаючы логіку праваў, значыць вы вже сталкнуліся з проблемамі, якія выступаюць унаследке памешання эўх двух падходаў. Давайце раз і назаўсёды ўсё выяснім.

Аутентыкацыя: паўтарна перакананне ў ідэнтычнасці

Аутентыкацыя — это этап, калі система падтверджвае, што корыстнік дэйсна ёсць тым, кім себе прызнае. Яна адпавядае на адну толькі запытанне: чы рэальна гэта ідэнтычнасць?

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

Тыповыя спосабы рэалізацыі аутентыкацыі включаюць:

  • Аутентыкацыя на адной паролі — наданыя аутентыкацыйныя дакументы паўтарна пераўераныя з адпаведным хэшаваным значэнням, якое зберагаецца.
  • Аутентыкацыя на токене (JWT) — падписаны токен, створанный пасля успешнага заходжання ў систему, які пазней пераўераны ў кожным наступным запытанні.
  • OAuth 2.0 / OpenID Connect — пераўеркі ідэнтычнасці аддаюцца зовнішнему прадаўцу, такаму як Google або GitHub.
  • Клучы API — фіксаваная таёмная строчка, якая ідентыфікуе конкрэтнага кліента або сервіс.
  • Багатафакторная аутэнтыкацыя (MFA) — дадзенны ўскладнены крок паўерырання (одноразовы код, дапаможны прыстаўак) які дадаецца да пароля
  • Што выглядае стандартны абмян паўерыранням:

    POST /api/login
    Content-Type: application/json
    
    {
      "email": "user@example.com",
      "password": "hashed_and_verified_serverside"
    }
    
    Response: 200 OK
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    

    Сервер паўерынае адправленыя данні пры входзе і, калі яны падтверджаныя, вяртае токен. Ад гэтага момента гэты токен служыць доказам аідэнтычнасці аб’екта, які выкарыстоўвае запыт, у кожным запыце:

    GET /api/profile
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    

    Мідлвэр, які адпавядае за паўерыну гэтага токена, зазвычай выглядае так:

    function authenticate(req, res, next) {
      const token = req.headers.authorization?.split(' ')[1];
      if (!token) return res.status(401).json({ error: 'No token provided' });
    
      jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
        if (err) return res.status(401).json({ error: 'Invalid or expired token' });
        req.user = decoded;
        next();
      });
    }
    

    Зверніце увагу на тое, што цей мідлвэр спецыяльна не включае. Ніколі ён не выначае, што можа робіць корыстнік. Ён проста паўтарае, што токен є легітымны, і прыўязвае декодаваны пэйлоад да об’екта запиту. Нічога большага не ўскладнюе завадзівкі аутэнтыкацыі.

    Автарызацыя: перакананне ў правах

    Автарызацыя запускаецца толькі пасля таго, як аутэнтыкацыя вже успела. Яна адпавядае на абсалютна іншы вопыт: чы розглядваная ідэнтычнасць мае право выконваць гэтую конкрэтную дзеянне?

    Дзеякі з распашчытных падходаў да структуравання автарызацыі:

    • Контроль даступу на адной засадзе ролей (RBAC) — правыя прыўязваюцца да ролей такіх, як admin, editor чы viewer
  • Контроль даступу на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю (ABAC) — рашэнні пра даступ вырахоўваныя на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю атрыбутах, такіх як вяласць дапартамента, регіон, вяласць рэсурса або нават час дзёны
  • Спісы кантролю даступу (ACL) — права адначасова визначаныя для кожнага пользователя і кожнага рэсурса
  • Дыяпазоны (OAuth) — сам токен кодуе, якія конкрэтныя операцыі дазволены, напрыклад read:messages або write:files
  • Тыповая перацэнка автарызаціі выкананая на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю на адміністрацыю запыту, які ўжо праўерыўся праз аутэнтыкацыю:

    function authorize(requiredRole) {
      return (req, res, next) => {
        if (req.user.role !== requiredRole) {
          return res.status(403).json({ error: 'Insufficient permissions' });
        }
        next();
      };
    }
    
    app.get('/api/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
      res.json({ data: 'sensitive admin data' });
    });
    

    Это два разныя функцыяй мідлвэра, якія керуюць двума рознымі задачамі і выконваныя за фіксаваным порядкам: authenticate выявляе, хто ўсё-такі здаў запит, а authorize вяршыць, чы розраховваецца на тое, што гэты аберант зможа працаваць далей. Запит можа перайсці праз першую пераканальню і ўсё-такі быць адхіленым праз другую — і такая адхіленне не мае нічынаго спраўа з ідэнтыфікацыяй.

    Коды статусу, якія фактычна праказваюць гэтую разніцу

    Сяродзе гэтага тэаретычны разлік ператвараецца на ўсё, што можна буквальна пераканаць у кодзе.

    401 Unauthorized — назва ўводзіць у глухы кут, але гэты код на самай працоўцы сигналізуе пра проблему з аутентыкацыяй. Гэта значыць, што сервер не атрымаў жадных крантыфікацый, альбо тые, якія ён атрымаў (вакол токена), є нэваліднымі або выйшлі з часу дзеяння. У гэты момант сервер не мае жадных паводзяў, хто самэй здаў запит.

    403 Forbidden — гэты код сігналізуе пра проблему з автарызацыяй. Сервер уже ведае, хто ўчаснік запыту; ён проста не дазволяе гэтай айдэнтыцы выканаць запрошаную дзеянне чыта прыйсці да запрошанага ресурсу.

    // Authentication failure — identity unknown or invalid
    res.status(401).json({ error: 'Invalid credentials' });
    
    // Authorization failure — identity known, permission denied
    res.status(403).json({ error: 'You do not have access to this resource' });
    

    Адправка коду 401 у разы, калі насправды ідзе пра проблему з правамі, — гэта частая памылка. Гэта прасіць кліента знову заўвайсці, хоць насправды всё з заўвайшчам у порядку — проста у акаунті няма неабходнай ролі. Лячна такая памылка можа ператворыць тое, што магло бы стаць простым рашэнням, у дзяўгі, некоректны процес адлучэння прычыны на боку кліента, калі шукаюць баг у заўвайшчы, якога няма.

    Дзе гэта відбываецца ў рэальных архітектурах

    Палеты дадзеных JWT часта сумешчваюць гэтыя аспекты без жадання каго-небудзь. Токен павінен хаваць даныя або ідэнтыфікацыі — такія як sub, email, iat і exp. Тое, чы роўна яго трэба таксама носіць даныя прывілеў, напрыклад role або scope, — гэта рашэнне па дизайну, якое залежыць ад системы. Размешчэнне ролей безпосередна ў токене ёсць зручным і дазволяе ухіліцца ад дадатковых пошукоў, але гэта караецца рэальнымі наследкамі: будзь-яя змена прывілеў пользователя не будзе застосавана, пакуль той токен не будзе выданы знова. Гэта справжняя кампенсацыя, а не шляхавацтво без негатыўных наследкаў.

    {
      "sub": "user_12345",
      "email": "user@example.com",
      "role": "editor",
      "iat": 1710000000,
      "exp": 1710003600
    }
    

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

    У раскладах з мікросервісамі аутантыкацыя зазвычай централізавана — яе адпрацоўвае гэйтвей или прадаўцу ідэнтычнасці, такі як Auth0, Keycloak чым AWS Cognito, які выдае токен. Аднак автарызацыя зазвычай залічваецца на адпаведным сервісе, які чытае інформацыю з гэтага токена і застосоўвае своія правіла. У результате адна і тая ж аутантыкацыйная дзейніца можа быць пераканана аднаццацю разных сервісоў по-разнаму, пры чым кожны з іх незалежна вырашае, што гэтая ідэнтычнасць можа робіць.

    Правіло, якое запобегае большасці багоў у безпэцы

    Автанацыя павінна выконвацца раней за автарызацыю, і гэтыя два процесы ніколі не павінны быць адналежна ўжоў у адной сумарнай перацэнке.

    // Correct: sequential, separate concerns
    app.post('/api/posts/:id/delete', authenticate, authorize('editor'), deleteHandler);
    
    // Wrong: conflating identity with permission in one step
    app.post('/api/posts/:id/delete', (req, res) => {
      if (req.headers.authorization === 'valid-token-and-also-admin') {
        // fragile, unscalable, and impossible to audit correctly
      }
    });
    

    Сістэма, якая рэгулярна атестуе, але пры этам прахоўвае автарызацыю, ідеальна параджуе айдэнтычнасць, а потым дазваляе кожной параджанай айдэнтычнасці робіць все, што яна хоча — таму атака на адны кантэкст з мягкімі правамі фактычна значыць атаку на весь системны кантэкст. Якща ж автарызацыя слабая, але автарызацыя рэгулярна налаштаваная, то выступае працоўны проблем: рэгулярна налаштаваныя правы працоўваюць над дверзямі, якія кожны можа працягнуць без жадных перакананняў.

    Обе складовыя павінны быть надзейнымі, застаўляцца у правым порядку і расследжваліся як аднае самастоятельнае пытанне. У гэтым і заключаецца весь разлік — і калі гэта будзе належным чынам адбітася ў вашых кодах статусу, ланцюгу мідлвэра і спосабе проектавання токенаў, цэлы клас бягоў безпекі проста зникае.

    Супакойлена літэратура

  • Перазьявленне падходу да React State: Дзе насправды павінны знаходзіцца вашы даны — У гэтым артыкуле пояснюецца, як зменшыць калекі ў React, пераносячы стацус у URL, DOM чыю выведзеныя значэння замест таго, каб часта викорыстоўваць useState.