Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Uwierzytelnianie przy użyciu JSON Web Token

Uwierzytelnianie przy użyciu JSON Web Token

W tej prezentacji omawiam typowe zastosowania JWT oraz pokazuję sposoby implementacji obsługi JWT zarówno po stronie klienta jak i serwera (na przykładzie OpenID Connect oraz uwierzytelniania REST API).

Krzysztof Kąkol

November 22, 2016
Tweet

More Decks by Krzysztof Kąkol

Other Decks in Programming

Transcript

  1. Autoryzacja (np. OAuth) Czy mam do czegoś prawo (dostęp do

    zasobów, wykonanie operacji, pobranie danych)? Uwierzytelnianie (np. OpenID Connect) Kim jestem? Czy na pewno jestem „po drugiej stronie”? Uwierzytelnianie vs autoryzacja 2016-11-22- 3 Uwierzytelnianie vs autoryzacja
  2. Zewnętrzny provider OpenID Nasz klient może wymagać stworzenia aplikacji, do

    której logowanie będzie możliwe wyłącznie poprzez providera OpenID. Po co o tym mówimy? 2016-11-22- 5 Po co o tym mówimy?
  3. Logowanie domenowe wewnątrz organizacji Aplikacje wewnętrzne, które wymagają logowania domenowego

    – potencjalne zagrożenie, jeśli nasza aplikacja wykorzystuje LDAP. Lepiej, żeby robił to pojedynczy provider OpenID. Po co o tym mówimy? 2016-11-22- 6 Po co o tym mówimy?
  4. Logowanie do REST API Jeśli API wymaga logowania, to można

    wysyłać JWT, jako identyfikator tożsamości zalogowanego użytkownika. Zawsze można zweryfikować autentyczność JWT. Po co o tym mówimy? 2016-11-22- 7 Po co o tym mówimy?
  5. Single Sign-On (SSO) Większość systemów SSO może wykorzystywać OpenID, a

    co za tym idzie – JWT, do identyfikacji tożsamości użytkownika. Po co o tym mówimy? 2016-11-22- 8 Po co o tym mówimy?
  6. Typowa zawartość JWT w OpenID Connect: { "sub" : "alice",

    #user ID "iss" : "https://openid.c2id.com", #kto wydał JWT "aud" : "client-12345", #docelowy odbiorca (opcjonalnie) "iat" : 1311280970, #czas utworzenia JWT "exp" : 1311281970 #czas upłynięcia ważności JWT } Struktura JSON Web Token 2016-11-22- 10 Źródło: http://connect2id.com/learn/openid-connect Struktura JSON Web Token
  7. Uwaga! JSON Web Token nie jest szyfrowany! Payload i header

    są zakodowane base64. JWT jest wyłącznie podpisany przy użyciu algorytmów szyfrujących (najczęściej asymetrycznych, np. RSA) Struktura JSON Web Token 2016-11-22- 11 Struktura JSON Web Token
  8. Na serwerze JWT podpisywany jest kluczem prywatnym (w przypadku szyfrowania

    asymetrycznego). U klienta weryfikacja jest możliwa przy użyciu klucza publicznego. Udostępnianie klucza publicznego możliwe jest w różny sposób, w OpenID dostępny jest JWKS URI. Weryfikacja JWT 2016-11-22- 12 Weryfikacja JWT
  9. Proces (authorization flow) 2016-11-22- 14 Web page OpenID Provider Przekierowanie

    do OpenID Providera Po autoryzacji powrót z jednorazowym kodem autoryzacji Prośba o ID token przy użyciu kodu autoryzacji Jeśli kod jest ważny, zwrotnie przesyłany jest JWT Proces (authorization flow)
  10. W akcji kontrolera: Klient OpenID 2016-11-22- 17 ... $openIdConnect =

    $this->get('openid.client.service'); $openIdConnect->authenticate(); $userObject = $openIdConnect->requestUserInfo('name'); ... Klient OpenID
  11. Serwis klienta OpenID: Klient OpenID 2016-11-22- 18 public function authenticate()

    { if (isset($_REQUEST["code"])) { $token_json = $this->requestTokens($_REQUEST["code"]); ... if (!$this->verifyJWTsignature($token_json->id_token)) { //throw exception here } ... } else { $this->requestAuthorization(); return false; } } Klient OpenID
  12. Weryfikacja JWT: Klient OpenID 2016-11-22- 19 $parts = explode(".", $jwt);

    $signature = $this->base64url_decode(array_pop($parts)); $header = json_decode($this->base64url_decode($parts[0])); $payload = implode(".", $parts); $jwks = json_decode($this->fetchURL($jwks_uri)); $hashtype = 'sha' . substr($header->alg, 2); $verified = $this->verifyRSAJWTsignature($hashtype, $this->get_key_for_header($jwks->keys, $header), $payload, $signature); Klient OpenID
  13. Weryfikacja JWT: Klient OpenID 2016-11-22- 20 private function verifyRSAJWTsignature($hashtype, $key,

    $payload, $signature) { $public_key_xml = "<RSAKeyValue>\r\n". " <Modulus>" . $this->b64url2b64($key->n) . "</Modulus>\r\n" . " <Exponent>" . $this->b64url2b64($key->e) . "</Exponent>\r\n" . "</RSAKeyValue>"; $rsa = new \phpseclib\Crypt\RSA(); $rsa->setHash($hashtype); $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); $rsa->signatureMode = \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; return $rsa->verify($payload, $signature); } Klient OpenID
  14. Krok 1: JWT 2016-11-22- 22 composer.json: "lexik/jwt-authentication-bundle” "friendsofsymfony/user-bundle” AppKernel.php: $bundles

    = [ … new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(), new FOS\UserBundle\FOSUserBundle(), … ]; JWT
  15. Krok 3: JWT 2016-11-22- 24 AppBundle\Entity\User.php: use FOS\UserBundle\Model\User as BaseUser;

    use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="fos_user") */ class User extends BaseUser { ... } JWT
  16. Krok 4: JWT 2016-11-22- 25 Wygeneruj klucze: $ mkdir -p

    app/var/jwt $ openssl genrsa -out app/var/jwt/private.pem -aes256 4096 $ openssl rsa -pubout -in app/var/jwt/private.pem -out app/var/jwt/public.pem parameters.yml: jwt_private_key_path: '%kernel.root_dir%/var/jwt/private.pem' jwt_public_key_path: '%kernel.root_dir%/var/jwt/public.pem' jwt_key_pass_phrase: 'your.password.phrase' jwt_token_ttl: 86400 JWT
  17. Krok 5: JWT 2016-11-22- 26 Konfiguracja bundli: fos_user: db_driver: orm

    firewall_name: main user_class: AppBundle\Entity\User lexik_jwt_authentication: private_key_path: %jwt_private_key_path% public_key_path: %jwt_public_key_path% pass_phrase: %jwt_key_pass_phrase% token_ttl: %jwt_token_ttl% JWT
  18. Krok 6: JWT 2016-11-22- 27 Konfiguracja firewalli w security.yml: login:

    pattern: ^/api/login stateless: true anonymous: true form_login: check_path: /api/login_check success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure require_previous_session: false api: pattern: ^/api stateless: true lexik_jwt: ~ JWT
  19. Krok 7: JWT 2016-11-22- 28 To wszystko! Jak przetestować? Np.

    Postmanem. 1. Należy utworzyć użytkownika (fos:user:create) 2. W Postmanie POST request do http://{domain}/api/login_check. Parametry x-www-form-urlencoded: _username, _password 3. Odpowiedź będzie wyglądała podobnie do poniższej: { "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpX..." } 4. Każdy call do API (czyli /api/*) musi posiadać nagłówek: Authorization: Bearer {token} gdzie {token} jest tym co otrzymaliśmy w punkcie 3. Jednocześnie {token} zawiera payload, czyli informacje o użytkowniku. JWT