Guardian (Βασικά)
JWT
Ένα JWT μπορεί να παρέχει ένα πλούσιο κέρμα για πιστοποίηση. Εκεί που πολλά συστήματα πιστοποίησης παρέχουν πρόσβαση μόνο σε ένα αναγνωριστικό αντικειμένου για τον πόρο, τα JWT παρέχουν αυτό μαζί με άλλες πληροφορίες όπως:
- Ποιός εξέδωσε το κέρμα
- Για ποιόν είναι το κέρμα
- Ποιό σύστημα θα πρέπει να χρησιμοποιήσει το κέρμα
- Τι ώρα εκδόθηκε
- Τι ώρα λήγει το κέρμα
Επιπρόσθετα σε αυτά τα πεδία η Guardian παρέχει μερικά άλλα πεδία για να διευκολύνει επιπρόσθετες λειτουργίες:
- Τι τύπος είναι το κέρμα
- Τι άδειες έχει ο κομιστής
Αυτά είναι μόνο τα βασικά πεδία σε ένα JWT. Έχετε τη δυνατότητα να προσθέσετε επιπρόσθετες πληροφορίες που χρειάζεται η εφαρμογή σας. Να θυμάστε μόνο να διατηρείτε τη συντομία τους, καθώς το JWT πρέπει να τις προσθέτει στην κεφαλίδα HTTP.
Αυτός ο πλουραλισμός σημαίνει ότι μπορείτε να μεταφέρετε JWTs στο σύστημά σας σαν ένα πλήρες σύστημα πιστοποιητικών.
Που να τα χρησιμοποιείτε
Τα JWT κέρματα μπορούν να χρησιμοποιηθούν για την πιστοποιήση οποιουδήποτε μέρους της εφαρμογής σας.
- Εφαρμογές μιας σελίδας
- Controllers (διαμέσω συνόδου του browser)
- Controllers (μέσω κεφαλίδων πιστοποίησης - API)
- Κανάλια Phoenix
- Αιτήματα Υπηρεσίας προς Υπηρεσία
- Ενδο-διεργασίας
- Πρόσβαση 3ων (OAuth)
- Λειτουργικότητα μνήμης επισκέπτη
- Άλλες διεπαφές - ακατέργαστο TCP, UDP, CLI κλπ
Τα κέρματα JWT μπορούν να χρησιμοποιηθούν οπουδήποτε στην εφαρμογή σας χρειάζεται να παρέχετε εξακριβωμένη πιστοποίηση.
Χρειάζεται να χρησιμοποιήσω βάση δεδομένων
Δεν χρειάζεται να παρακολουθείτε τα JWT με μια βάση δεδομένων. Μπορείτε απλά να στηριχθείτε στις χρονοσημάνσεις έκδοσης και λήξης για να χειριστείτε την πρόσβαση. Συχνά θα καταλήξετε να χρησιμοποιείτε βάση δεδομένων για να βρείτε τον πόρο χρήστη αλλά το ίδιο το JWT δεν το απαιτεί.
Για παράδειγμα, αν έπρεπε να χρησιμοποιήσετε ένα JWT για να πιστοποιήσετε την επικοινωνία σε μια υποδοχή (socket) UDP πιθανότατα δεν θα χρησιμοποιούσατε μια βάση δεδομένων. Κωδικοποιήστε όλες τις πληροφορίες που χρειάζεστε απευθείας στο κέρμα όταν το εκδώσετε. Όταν το επικυρώσετε (ελέγξτε ότι έχει υπογραφεί σωστά) είστε καλυμμένοι.
Μπορείτε πάντως να χρησιμοποιήσετε μια βάση δεδομένων για να παρακολουθήσετε ένα JWT. Αν το κάνετε, αποκτάτε την ικανότητα να επικυρώνετε ότι το κέρμα είναι ακόμα έγκυρο - δηλαδή - ότι δεν έχει ανακαλεστεί. Η θα μπορούσατε να χρησιμοποιήσετε τις εγγραφές στη βάση δεδομένων για να εξαναγκάσετε μια έξοδο όλων των κερμάτων για το χρήστη. Αυτό γίνεται εύκολο στην Guardian με τη χρήση της GuardianDb. Η GuardiadnDb χρησιμοποιεί τα ‘Hooks’ της Guardian για να κάνει ελέγχους επικύρωσης, να αποθηκεύσει και να διαγράψει από τη βάση δεδομένων. Θα το δούμε αυτό αργότερα.
Εγκατάσταση
Υπάρχουν πολλές επιλογές για την εγκατάσταση της Guardian. Θα τις δούμε όλες κάποια στιγμή αλλά ας ξεκινήσουμε με μια απλή εγκατάσταση.
Μινιμαλιστική Εγκατάσταση
Για να ξεκινήσετε υπάρχει μια πλειάδα πραγμάτων που θα χρειαστείτε.
Ρυθμίσεις
mix.exs
def application do
[
mod: {MyApp, []},
applications: [:guardian, ...]
]
end
def deps do
[
{guardian: "~> x.x"},
...
]
end
config/config.exs
# σε κάθε αρχείο ρυθμίσεων περιβάλλοντος θα πρέπει να το αντικαταστήσετε αυτό αν είναι εξωτερικό
config :guardian, Guardian,
issuer: "MyAppId",
secret_key: Mix.env(),
serializer: MyApp.GuardianSerializer
Αυτό είναι το ελάχιστο σετ πληροφοριών που χρειάζεται να δώσετε στην Guardian για να λειτουργήσει.
Δεν θα πρέπει να κωδικοποιήσετε το μυστικό κλειδί σας κατευθείαν στο αρχείο ρυθμίσεων κορυφαίου επιπέδου.
Αντίθετα, κάθε περιβάλλον πρέπει να έχει το δικό του κλειδί.
Είναι συχνό να χρησιμοποιείτε το περιβάλλον Mix για μυστικά κλειδιά στα περιβάλλοντα dev και test.
Στα Staging και Production όμως, πρέπει να χρησιμοποιείτε ισχυρά μυστικά κλειδιά.
(π.χ. που έχουν παραχθεί με την mix phoenix.gen.secret
)
lib/my_app/guardian_serializer.ex
defmodule MyApp.GuardianSerializer do
@behaviour Guardian.Serializer
alias MyApp.Repo
alias MyApp.User
def for_token(user = %User{}), do: {:ok, "User:#{user.id}"}
def for_token(_), do: {:error, "Unknown resource type"}
def from_token("User:" <> id), do: {:ok, Repo.get(User, id)}
def from_token(_), do: {:error, "Unknown resource type"}
end
Ο serializer σας είναι υπέυθυνος για την έυρεση της πηγής που ορίζεται στο πεδίο sub
(υποκείμενο).
Αυτό θα μπορεί να είναι μια αναζήτηση στη βάση δεδομένων, ένα API, ή ακόμα ένα απλό αλφαριθμητικό.
Είναι επίσης υπεύθυνος για την γραμμικοποίηση ενός πόρου στο πεδίο sub
.
Αυτό ήταν όλο για την ελάχιστη ρύθμιση. Υπάρχουν πολλά ακόμα που μπορείτε να κάνετε, αν τα χρειάζεστε, αλλά αυτά αρκούν για να ξεκινήσετε.
Χρήση Εφαρμογής
Τώρα που έχουμε τις ρυθμίσεις στη θέση τους για να χρησιμοποιήσουμε την Guardian, πρέπει να την ενσωματώσουμε στην εφαρμογή. Από τη στιγμή που αυτή είναι η ελάχιστη ρύθμιση, ας αναλογιστούμε πρώτα τις αιτήσεις HTTP.
Αιτήσεις HTTP
Η Guardian παρέχει ένα σύνολο Plugs για να διευκολύνει την ενσωμάτωση σε αιτήσεις HTTP. Μπορείτε να μάθετε για το Plug σε ένα άλλο μάθημα. Η Guardian δεν χρειάζεται την Phoenix, αλλά η χρήση της Phoenix κάνει ευκολότερη την παρουσίαση των ακόλουθων παραδειγμάτων.
Ο πιο εύκολος τρόπος να ενσωματώσετε στο HTTP είναι μέσω του δρομολογητή (router). Από τη στιγμή που οι ενσωματώσεις της Guardian στο HTTP είναι όλες βασισμένες σε plugs, μπορείτε να τις χρησιμοποιήσετε οπουδήποτε μπορεί να χρησιμοποιηθεί ένα plug.
Η γενική ροή ενός plug της Guardian είναι:
- Εύρεση ενός κέρματος στην αίτηση (κάπου) και επικύρωσή του: ‘Verify*’ plugs
-
Προεραιτικά φόρτωση του πόρου που ορίζεται στο κέρμα:
LoadResource
plug -
Βεβαίωση ότι υπάρχει ένα έγκυρο κέρμα για την αίτηση και αρνήση πρόσβασης αν όχι:
EnsureAuthenticated
plug
Για να καλυφθούν όλες οι ανάγκες των προγραμματιστών εφαρμογών, η Guardian υλοποιεί αυτές τις φάσεις ξεχωριστά.
Για να βρείτε το κέρμα χρησιμοποιείστε τα plugs Verify*
.
Ας δημιουργήσουμε μερικούς αγωγούς.
pipeline :maybe_browser_auth do
plug(Guardian.Plug.VerifySession)
plug(Guardian.Plug.VerifyHeader, realm: "Bearer")
plug(Guardian.Plug.LoadResource)
end
pipeline :ensure_authed_access do
plug(Guardian.Plug.EnsureAuthenticated, %{"typ" => "access", handler: MyApp.HttpErrorHandler})
end
Αυτοί οι αγωγοί μπορούν να χρησιμοποιηθούν για να συνθέσουν διαφορετικές απαιτήσεις πιστοποίησης. Ο πρώτος αγωγός προσπαθεί να βρει ένα κέρμα πρώτα στη συνεδρία και στη συνέχεια οπισθοχωρεί σε μια κεφαλίδα. Αν βρει ένα, τότε σας φορτώνει τον πόρο.
Ο δεύτερος αγωγός απαιτεί ότι υπάρχει ένα έγκυρο, επικυρωμένο κέρμα και ότι είναι τύπου “access”. Για να τα χρησιμοποιήσετε, προσθέστε τα στο πεδίο δράσης σας.
scope "/", MyApp do
pipe_through([:browser, :maybe_browser_auth])
get("/login", LoginController, :new)
post("/login", LoginController, :create)
delete("/login", LoginController, :delete)
end
scope "/", MyApp do
pipe_through([:browser, :maybe_browser_auth, :ensure_authed_access])
resource("/protected/things", ProtectedController)
end
Οι δρομολογητές εισόδου παραπάνω θα έχουν τον πιστοποιημένο χρήστη αν υπάρχει. Το δεύτερο πεδίο δράσης βεβαιώνει ότι ένα έγκυρο κέρμα περνάει σε όλες τις λειτουργίες. Δεν χρειάζεται να τις βάλετε στους αγωγούς, θα μπορούσατε να τις βάλετε στους controlllers σας για περισσότερο ευλύγιστη παραμετροποίηση αλλά εδώ φτάχνουμε μια μινιμαλιστική εγκατάσταση.
Μας λείπει ένα κομμάτι ως τώρα.
Ο χειριστής σφαλμάτων που προσθέσαμε στο plug EnsureAuthenticated
.
Αυτή είναι μια πολύ απλή ενότητα που απαντάει στα
-
unauthenticated/2
-
unauthorized/2
Και οι δύο αυτές συναρτήσεις λαμβάνουν μια δομή Plug.Conn και ένα χάρτη παραμέτρων και θα πρέπει να χειριστούν τα επιμέρους σφάλματά τους. Μπορείτε ακόμα να χρησιμοποιήσετε έναν Phoenix controller!
Στον controller
Μέσα στον controller, υπάρχουν μερικές επιλογές για το πως θα έχετε πρόσβαση στον τρέχοντα εισηγμένο χρήστη. Ας αρχίσουμε με τον απλούστερο.
defmodule MyApp.MyController do
use MyApp.Web, :controller
use Guardian.Phoenix.Controller
def some_action(conn, params, user, claims) do
# Κάντε διάφορα
end
end
Χρησιμοποιώντας την ενότητα Guardian.Phoenix.Controller
, οι λειτουργίες σας θα λαμβάνουν δύο περαιτέρω ορίσματα στα οποία μπορείτε να αντιπαραβληθείτε.
Θυμηθείτε, αν δεν βεβαιωθήκατε για την πιστοποίηση μπορεί να έχετε έναν κενό χρήστη και αξιώσεις.
Η άλλη - η περισσότερο ευέλικτη έκδοση - είναι να χρησιμοποιήσετε τις βοηθητικές plugs της Guardian.
defmodule MyApp.MyController do
use MyApp.Web, :controller
def some_action(conn, params) do
if Guardian.Plug.authenticated?(conn) do
user = Guardian.Plug.current_resource(conn)
else
# Κανένας χρήστης
end
end
end
Είσοδος/Έξοδος
Η είσοδος και η έξοδος μιας συνόδου browser είναι πολύ απλή. Στον controller εισόδου σας:
def create(conn, params) do
case find_the_user_and_verify_them_from_params(params) do
{:ok, user} ->
# Χρησιμοποιήστε κέρματα πρόσβασης.
# Άλλα κέρματα μπορούν να χρησιμοποιηθούν, όπως τα :refresh κ.α.
conn
|> Guardian.Plug.sign_in(user, :access)
|> respond_somehow()
{:error, reason} ->
nil
# χειριστείτε την μη επικύρωση των διαπιστευτηρίων του χρήστη.
end
end
def delete(conn, params) do
conn
|> Guardian.Plug.sign_out()
|> respond_somehow()
end
Όταν χρησιμοποιείτε το API εισόδου, είναι ελαφρώς διαφορετικό καθώς δεν υπάρχει σύνοδος και πρέπει να παρέχετε το μη επεξεργασμένο κέρμα πίσω στον πελάτη.
Για είσοδο API μάλλον θα χρησιμοποιείτε την κεφαλίδα Authorization
για να παρέχετε το κέρμα στην εφαρμογή σας.
Αυτή η μέθοδος είναι χρήσιμη όταν δεν σκοπεύετε να χρησιμοποιήσετε μια σύνοδο.
def create(conn, params) do
case find_the_user_and_verify_them_from_params(params) do
{:ok, user} ->
{:ok, jwt, _claims} = Guardian.encode_and_sign(user, :access)
conn |> respond_somehow(%{token: jwt})
{:error, reason} ->
# χειριστείτε την μη επικύρωση των διαπιστευτηρίων του χρήστη
end
end
def delete(conn, params) do
jwt = Guardian.Plug.current_token(conn)
Guardian.revoke!(jwt)
respond_somehow(conn)
end
Η είσοδος συνόδου browser καλεί την encode_and_sign
κάτω από το καπώ έτσι ώστε να μπορείτε να τις χρησιμοποιήσετε με τον ίδιο τρόπο.
Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!