Είναι η πρώτη φορά που υλοποιώ token mechanism και απλώς ήθελα να ρωτήσω για σιγουριά.
Αυτό που θέλω να κάνω είναι ο χρήστης να κάνει login μια φορά και για κάποιο χρονικό διάστημα να μπορεί να έχει πρόσβαση αυτόματα σε υπηρεσίες που χρειάζονται authenticate.
Όταν ο χρήστης κάνει login στην ουσία δημιουργώ ένα string με το όνομα χρήστη + 20 τυχαίους χαρακτήρες. Επιστρέφω στον χρήστη αυτό το token και κάθε φορά που επισκέπτεται μια σελίδα αυτόματα περνιέται στην GET η παράμετρος token την οποία χρησιμοποιώ για να κάνω authenticate αυτοματα behine the scene.
Αυτός είναι ο κώδικας που κάνει generate το token για έναν χρήστη που έχει κάνει με επιτυχία login.
Το token θα είναι valid για τα πρώτα 5 λεπτά.Κώδικας://Token Class. class VampCmsToken { //Properties. private $m_user = null; private $m_chars = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'); //Constructor. function __construct($user) { $this->m_user = $user; } //Generate token. public function generate() { //Start the token with the username. $token = $this->m_user->m_username; //Append 20 random characters to the token. for ($i = 0; $i < 20; $i++) $token .= $this->m_chars[ rand(0, count($this->m_chars)-1) ]; return $token; } }
Πιστεύω θα κάνει μια χαρά την δουλειά του και από ασφάλεια δεν είμαι σίγουρος. Πάντως μου φαίνεται αδύνατον κάποιος να μαντέψει το token εκτός αν καταφέρει να το κλέψει με κάποιο τρόπο και να το χρησιμοποιηση μέσα σε αυτά τα 5 λεπτά.
Εμφάνιση 1-15 από 18
-
30-09-19, 17:54 Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #1
-
30-09-19, 22:20 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #2
Αν ένας χρήστης στείλει ένα τυχαίο token πώς ελέγχεις αν είναι valid;
Μπορείς να χρησιμοποιήσεις JWT tokens για να κάνεις την ίδια δουλειά, που περιέχουν και πληροφορίες για τον χρήστη που το λαμβάνει. Ουσιαστικά το JWT είναι ένα JSON με μία ψηφιακή υπογραφή από κάτω. Οι πληροφορίες μπορούν να διαβαστούν από όλους, αλλά κανένας δεν μπορεί να τις πειράξει, γιατί θα χαλάσει και η υπογραφή. Έτσι μπορείς να στείλεις στον χρήστη πληροφορίες (πχ το όνομα χρήστη, display name, ημερομηνία δημιουργίας του token κτλ) ώστε να εμφανιστούν και κάθε φορά που ο χρήστης θέλει να κάνει κάτι πρέπει να σου στείλει και το token μαζί. Εσύ ελέγχεις αν είναι valid, αν έχει λήξει κτλ.
Υποθέτω γράφεις PHP, οπότε μπορείς να δεις αυτό.
-
30-09-19, 22:27 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #3
Το τοκεν σε hash δεν θα είναι; Που το πρόβλημα αν το κλέψει κάποιος;
-
30-09-19, 23:30 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #4
Θα φτιάξω ένα table (tokens) με σχήμα (token TEXT PRIMARY KEY, creation_time DATETIME, expires INT) και θα ελέγχων αν το token που μου δίνει ο χρήστης υπάρχει στο table. Αν υπάρχει και η διαφορά του current time και creation_time δεν ξεπερνάει το expires (σε δευτερόλεπτα) τότε του δίνω access, διαφορετικά τον κάνω redirect στο login page.
-
01-10-19, 08:28 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #5
Δεν θα πρότεινα να κάνεις τον πίνακα με το token key σαν primary key. Αν θελεις να το κάνεις έτσι βάλε το username σαν unique index., όχι το token. Αλλιώς μπορεί να πέσεις σε exception επειδή μετα από καιρό κατά τύχη έφτιαξες το ίδιο τοκεν. Επίσης ο πίνακας σου θα μεγαλώνει συνεχώς ενώ δεν το χρειάζεσαι ή θα πρέπει να έχεις extra overhead σε κάθε ερώτημα για να ψάχνεις όλο τον πίνακα και να διαγράφεις τα ληγμένα. Με αυτόν τον τρόπο βέβαια ένας χρήστης δεν θα μπορεί να ανοίξει πολλά session καθώς το ένα token θα κάνει overwrite το άλλο.
-
01-10-19, 10:13 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #6
Τα tokens που έχουν λήξει θα διαγράφονται από τον πίνακα. Τώρα για παράλληλα sessions ναι δεν θα δουλεύει γιατί όντως για έναν συγκεκριμένο χρήστη μπορεί να τύχει να δημιουργειθεί το ίδιο token (αν και η πιθανότητα είναι πάρα πολύ μικρή) αλλά για το site που σχεδιάζω το συγκεκριμένο login θα είναι κυριολεκτικά για 2-3 χρήστες μόνο. Αλλά αυτό το λύνω και στο μέλλον δεν υπάρχει θέμα.
- - - Updated - - -
Τώρα δυσκολεύομαι λίγο στην υλοποίηση της διαγραφής. Αυτό που θέλω να κάνω είναι μετά την δημιουργία ενώς token να ενεργοποιείτε ένας timer και μετά από κάποιον συγκεκριμένο χρόνο να καλείτε ένας trigger ο οποίως να διαγράφει το token από τον πίνακα.
Ας πουμε ότι σε mysql φτιάχνω έναν trigger ο οποίος ενεργοποιείτε όταν κάνω ένα insert στο tokens table. Μπορώ να υλοποιήσω έναν timer που να τρέχει για το συγκεκριμένο inserted token και όταν λίξει ο timer να διαγράφω το token από τον πίνακα;
-
01-10-19, 12:23 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #7
Δεν έχω mysql για να το δοκιμάσω αλλά λογικά κάτι τέτοιο θα σου δουλέψει:
Κώδικας:CREATE TRIGGER tokens_insert AFTER INSERT ON tokens FOR EACH ROW BEGIN CREATE EVENT delete_token ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 MINUTE DO DELETE FROM tokens WHERE token = NEW.token; END
- - - Updated - - -
Πάντως νομίζω ότι μπλέκεις χωρίς λόγο με τη βάση ενώ μπορείς να το κάνεις πολύ πιο εύκολα με ένα JWT.
-
01-10-19, 14:57 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #8
@babaliaris Σε αφήνει το web hosting event στο mysql? Αν όχι πήγαινε σε cron. Ελπίζω να το γνωρίζεις.
-
01-10-19, 16:20 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #9
-
01-10-19, 16:21 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #10
Αν πας στο phpmyadmin και πατήσεις event system (νμζ ή events) θα δεις. Τα φτηνιάρικα hostings δεν στο παρέχουν
-
02-10-19, 20:05 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #11
Α κατάλαβα. Φθηνό hosting έχω.
- - - Updated - - -
Τελικά έκανα δική μου υλοποίηση χωρίς να χρειάζεται mysql events.
Κώδικας://Validate token. function validateToken($cmsToken) { $t_start = new DateTime($cmsToken->m_start); $t_expires = new DateTime($cmsToken->m_expires); $today = new DateTime(); $today_time_zero = new DateTime(); $today_time_zero->setTime(0,0,0); $time_diff = $today->format("U") - $t_start->format("U"); $expire_time = abs($t_expires->format("U") - $today_time_zero->format("U")); //Validation failed. if ($time_diff > $expire_time) return false; return true; } //Authenticate using token. if(isset($_GET["token"])) { //Clear Expired Tokens. $vamp_core->m_db->clearTokens($_GET["token"]); //Search the token in the database. $tokens = $vamp_core->m_db->getTokens($_GET["token"]); //Tokens found. if (count($tokens) > 0) { $output->token = $_GET["token"]; $output->ignore= true; for ($i = 0; $i < count($tokens); $i++) { if (validateToken($tokens[$i])) { $output->validate=true; break; } } } //Token not found. else { //Get or create authenticate error string. $cms_string = $vamp_core->m_db->getCMSstring ( $vamp_core->getLang(), "vamp_cms_authenticate_token_not_found", "The token was not found." ); $output->error = $cms_string->m_value; } }
To table sheme είναι αυτό:
Κώδικας:USE vamp_cms_db; CREATE TABLE IF NOT EXISTS tb_tokens ( m_id INT AUTO_INCREMENT PRIMARY KEY, m_userid INT NOT NULL, m_token TEXT NOT NULL, m_start DATETIME NOT NULL, m_expires DATETIME NOT NULL, FOREIGN KEY (m_userid) REFERENCES tb_users (m_id) );
- - - Updated - - -
Τελικά είδα υλοποίηση του JWT και σκέφτηκα κάτι καλύτερο και παρόμοιο. Δεν χρειάζεται να χρησιμοποιώ βάση δεδομένων. Η υλοποίηση μου είναι εξής:
Δημιουργώ ένα token της μορφής username;remoteaddr;userid;creation_timestamp;expiration_timestamp
To creation_timestamp και expiration_timestamp είναι χρόνοι σε δευτερόλεπτα που περιγράφουν συγκεκριμένη ημερομηνία και ώρα και τα χρησιμοποιώ για να δω αν το token
έχει λήξει η όχι.
Το remoteaddr το χρησιμοποιώ για να είμαι σίγουρος ότι είναι η ίδια συσκευή που έκανε νωρίτερα login και δημιούργησε το token.
To username και userid τα χρησιμοποιώ για να ξέρω ποιος χρήστης είναι. (Το userid είναι το primary key στην βάση)
Τώρα βέβαια αν αφήσω το token έτσι ο hacker μπορεί άνετα να δημιουργήσει το δικό του token και να ξεγελάσει το σύστημα (αφού το σύστημα δεν καταγράφει τα tokens που δημιουργεί), αρκεί να ξέρει το username στον οποίον θέλει
να αποκτήσει πρόσβαση. Για αυτόν το λόγο κρυπτογραφώ ολόκληρο το token (εκτός το username).
Η κρυπτογράφηση γίνεται με private key τον κωδικό πρόσβασης του user. Στην συνέχεια στέλνεται πίσω σαν απάντηση στον browser και πλέον σε κάθε http request στον server μου από εδώ και πέρα δίνεται και το token σαν παράμετρος GET.
Στην συνέχεια αποκρυπτογραφώ το token χρησιμοποιόντας τον κωδικό του χρήστη (το username στο token είναι raw και όχι κρυπτογραφημενο άρα ξέρω ποιος είναι και ποιος είναι ο κωδικός του).
Και τέλος ελέγχω το validation του token αν δεν έχει γίνει expired και αν ο ip που είναι στο token ταιριάζει με τον ip που έκανε το http request δίνοντας το token.
Ακόμα δεν έχω τελειώσει την υλοποίηση (μου έχει μείνει η κρυπτογράφηση του ip στο token και το validate του ip) αλλά μπορείτε να δείτε παρακάτω τι έχω κάνει μέχρι τώρα:
Κώδικας:<?php //Initialize the database access. require __DIR__ . "/core.php"; $vamp_core = new VampCmsCore("babaliaris", "123", "vamp_cms_db"); //Token Class. class VampCmsTokenHandler { public $chars = array('a'=>0, 'b'=>1, 'c'=>2, 'd'=>3, 'e'=>4, 'f'=>5, 'g'=>6 , 'h'=>7, 'i'=>8, 'j'=>9, 'k'=>10, 'l'=>11, 'm'=>12, 'n'=>13, 'o'=>14, 'p'=>15 , 'q'=>16, 'r'=>17, 's'=>18, 't'=>19, 'u'=>20, 'v'=>21, 'w'=>22, 'x'=>23, 'y'=>24 , 'z'=>25, '0'=>0, '1'=>1, '2'=>2, '3'=>3, '4'=>4, '5'=>5, '6'=>6, '7'=>7, '8'=>8 , '9'=>9); //Generate token. public function generate($user) { //Create the datetimes. $now = new DateTime(); $exp = new DateTime(); $exp->setTime(0, 0, 10); //Start the token with the username. $token = $user->m_username.";". $_SERVER["REMOTE_ADDR"]. ";" . (string)$user->m_id . ";" . (string)$now->format("U"). ";" . (string)$exp->format("U"); return $token; } //Creation of Private Key. private function createKey($user) { //Data. $pass = str_split($user->m_password); $private_key = 0; //Create foreach ($pass as $char) $private_key += $this->chars[$char]; return $private_key; } //Encrypt Token. public function encrypt($token, $user) { //Data. $token_data = explode(";", $token); $encrypted = $token_data[0] . ";" .$token_data[1]; $p_key = $this->createKey($user); //Encrypt all the token fields, except username and ip. for($i=2; $i < count($token_data); $i++) { $encrypted_data = $token_data[$i] + $p_key; $encrypted .= ";" . (string)$encrypted_data; } return $encrypted; } //Decrypt Token. public function decrypt($encrypted_token, $user) { $token_data = explode(";", $encrypted_token); $token = $token_data[0] . ";" .$token_data[1]; $p_key = $this->createKey($user); //Decrypt all the token fields, except username and ip. for($i=2; $i < count($token_data); $i++) { $data = $token_data[$i] - $p_key; $token .= ";" . (string)$data; } return $token; } //Validate Token. public function validate($token) { //Data. $token_data = explode(";", $token); $t_start = $token_data[3]; $t_expires = $token_data[4]; $today = new DateTime(); $today_time_zero = new DateTime(); $today_time_zero->setTime(0,0,0); //Calculate total time since token was created. $time_diff = $today->format("U") - $t_start; //Calculate expiration time. $expire_time = abs($t_expires - $today_time_zero->format("U")); //Validation failed. if ($time_diff > $expire_time) return false; return true; } } //This will be returned. class VampCmsTokenJson { public $error = ""; public $token = ""; public $ignore=false; public $validate=false; public $msg=""; } //Create the output object. $output = new VampCmsTokenJson(); //Authenticate using token. if(isset($_GET["token"])) { $generetor = new VampCmsTokenHandler(); //This is a fake test to create a token. //MUST BE IMPLEMENTED. if ($_GET["token"] == "0") { $user = new VampCmsUser(); $user->m_id = 100; $user->m_username = "babaliaris"; $user->m_password = "zervos04011996"; $user->m_isroot = true; $token = $generetor->generate($user); $encr = $generetor->encrypt($token, $user); $output->token = $token; } //Test token validation. else { $output->msg=$generetor->validate($_GET["token"]); } } //Authenticate using username and password. else { //Check if required arguments have been passed. if ( isset($_POST["username"]) && isset($_POST["password"]) ) { //Get the user. $user = $vamp_core->m_db->getUser($_POST["username"]); //User exists. if ($user != null) { //Password matches. if ($user->m_password == $_POST["password"]) { $token = new VampCmsTokenGenerator($user); $output->token = $token->generate($user); } //Password is invalid. else { //Get or create authenticate error string. $cms_string = $vamp_core->m_db->getCMSstring ( $vamp_core->getLang(), "vamp_cms_authenticate_invalid_pass", "Invalid password." ); $output->error = $cms_string->m_value; } } //User does not exists. else { //Get or create authenticate error string. $cms_string = $vamp_core->m_db->getCMSstring ( $vamp_core->getLang(), "vamp_cms_authenticate_invalid_user", "This username does not exist." ); $output->error = $cms_string->m_value . "Username: '".$_POST["username"]."'"; } } //Required arguments have not been set. else { //Get or create authenticate error string. $cms_string = $vamp_core->m_db->getCMSstring ( $vamp_core->getLang(), "vamp_cms_authenticate_not_args", "Required arguments (username, password) have not been passed." ); $output->error = $cms_string->m_value; } } //Echo the output as a json object. echo json_encode($output); ?>
Αν και η ο αλγοριθμός κρυπτογράφησης είναι δικός μου σχετικά απλός, πάλι δεν νομίζω να παράβιαζεται. Τώρα αν είναι πολύ έξυπνος σίγουρα μπορει να πειράξει τα πεδία creation_timestamp και expiration_timestamp και να ξεγελάσει το expiration αλλά ακόμα και να το καταφέρει αυτό δεν μπορεί να ξεγελάσει τον ip εκτός και αν συνδέετε από το ίδιο δύκτιο από το οποίο δημιουργήθηκε το token. Η βέβαια αν ξέρει με κάποιον τρόπο ποιο είναι το κτυπτογραφημένο token.
Ο λόγος που μπορεί να πειράξει τα creation_timestamp και expiration_timestamp είναι ότι οι τιμές τους δεν χρειάζεται να είναι ακριβής, αρκεί το χρονικό
περιθώριο που περιγράφουν να είναι τέτοιο έτσι ώστε το creation_timestamp - today να μην ξεπερνάει το expiration_timestamp-today. Ακόμα και να μην μπορέσει να αποκρυπτογραφήσει αυτά τα μυνήματα, από την στιγμή που η κρυπτογράφηση δημιουργεί ακέραιους αριθμός, μπορεί να τα βολέψει με κάποιον τρόπο έτσι ώστε να περνάνε το validation. Αλλά και πάλι, αφού δεν μπορεί να πηράξει τον ip δεν έχει σημασία.
- - - Updated - - -
Το ξέρω ότι θα μου πείτε να χρησημοποιώ έτοιμα πράμματα (και έχετε δίκαιο), αλλά πολλές φόρες κάνω και δικές μου υλοποιήσεις για να μαθαίνω. Βέβαια θα χρησιμοιποιήσω κάτι ποιο αξιόπιστο σε αληθινή εφαρμογή.Τελευταία επεξεργασία από το μέλος babaliaris : 02-10-19 στις 20:02.
-
02-10-19, 23:28 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #12username;remoteaddr;userid;creation_timestamp;expiration_timestamp
Κάθε φορά που θα λαμβάνεις το token το κάνεις validate με την υπογραφή και ξέρεις αν είναι έγκυρο ή όχι. Δεν χρειάζεται να έχεις πολλά κλειδιά, για κάθε χρήστη. Ένα κλειδί για την εφαρμογή, που φτιάχνει token και ελέγχει αν ειναι έγκυρο ή όχι. Με αυτόν τον τρόπο ακόμα και ο client ξέρει το expiration οπότε δεν χρειάζεται να στείλει το αίτημα για να τον απορρίψεις, το απορρίπτει μόνος του.
Καλα κάνεις και χρησιμοποιείς δικές σου υλοποιήσεις για εκμάθηση, αλλά στην παραγωγή καλύτερα να έχεις σίγουρες λύσεις.
Αν δεις όλο το JWT έχει και την υπογραφή μέσα, όχι κρυπτογραφιση όλου του token.
-
05-10-19, 11:31 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #13
Αν μετά από το login βάλω το token σαν html attribute στην σελίδα που κάνω redirect τον χρήστη (αντί να το σώζω σε κάποιο cookie) υπάρχει πρόβλημα;
Δηλαδή. Ο χρήστης μπαίνει κάνει login και στην επυτιχία κάνω redirect τον χρήστη κάπως έτσι:Κώδικας:"Location: "._$GET["redirect_link"]."?token=".$token
Κώδικας:<body my_token="<?php echo _$GET["token"]?>"> </body>
-
05-10-19, 12:49 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #14
Αν το κάνεις με attribute ο σωστός τρόπος θα είναι με custom html data attributes, ώστε να μην μπερδεύεις τα standard html attributes με τα δικά σου custom. Καλύτερα πάντως θα είναι να το στέλνεις με cookie το token, ώστε αυτό να αποστέλλεται αυτόματα με οποιοδήποτε request γίνεται, χωρίς να το στέλνεις εσύ manually. Επίσης στο cookie μπορείς να βάλεις expiration ώστε να το "ξεχνάει" ο client αυτόματα μόλις λήξει.
None of our beliefs are quite true; all have at least a penumbra of vagueness and error.
Bertrand Russell
-
05-10-19, 13:03 Απάντηση: Web Dev: Είναι αξιόπιστη αυτήν η μέθοδος δημιουργίας token; #15
Αυτό βασικά θα κάνει τα πράμματα ποιο εύκολα. Πρέπει να ζητάω άδεια για cookies σωστά; Αν ζητάω αδειά και ο χρήστης την απορίπτει αλλά εγώ τον "γράφω" και δημιουργώ ούτως η άλλος θα με πιάσουν;
Επίσης κάτι τελευταίο. Εφόσον χρησιμοποιώ ssl πειράζει αν δεν χρησιμοποιώ digital signature? Στην ουσία στο token cookie θα αποθηκεύω ποιος χρήστης έκανε login και ότι άλλη πληροφορία θέλω και τελείωσα. Αν δεν κάνω λάθος to jwt ποιο πολύ είναι για api authentication, στην δική μου περίπτωση φτιάχνω ένα cookie και τελείωσα.
Bookmarks