Verificatie is een van de belangrijkste onderdelen van elke webtoepassing. In deze zelfstudie bespreken we token-gebaseerde authenticatiesystemen en hoe deze verschillen van traditionele inlogsystemen. Aan het einde van deze tutorial ziet u een volledig werkende demo geschreven in AngularJS en NodeJS.
U kunt ook een brede selectie van kant-en-klare authenticatiescripts en apps vinden op Envato Market, zoals:
Of, als u worstelt met een bug in uw AngularJS-code, kunt u deze bij Araneux op Envato Studio indienen om deze te laten repareren.
Voordat we verder gaan met een token-gebaseerd authenticatiesysteem, laten we eerst een traditioneel authenticatiesysteem bekijken.
Tot nu toe is alles in orde. De webtoepassing werkt goed en kan gebruikers verifiëren zodat ze toegang hebben tot beperkte eindpunten; Wat gebeurt er echter als u voor uw toepassing een andere client wilt ontwikkelen, zeg voor Android? Kunt u de huidige toepassing gebruiken om mobiele clients te verifiëren en beperkte inhoud weer te geven? Zoals het er nu uitziet, nee. Daar zijn twee belangrijke redenen voor:
In dit geval heeft u een client-onafhankelijke applicatie nodig.
In token-gebaseerde authenticatie worden cookies en sessies niet gebruikt. Een token wordt gebruikt voor het verifiëren van een gebruiker voor elke aanvraag aan de server. Laten we het eerste scenario opnieuw ontwerpen met token-gebaseerde authenticatie.
Het gebruikt de volgende controlestroom:
In dit geval hebben we geen geretourneerde sessie of cookie en hebben we geen HTML-inhoud geretourneerd. Dat betekent dat we deze architectuur voor elke klant voor een specifieke toepassing kunnen gebruiken. U kunt het architectuurschema hieronder bekijken:
Dus, wat is deze JWT?
JWT staat voor JSON Web Token en is een tokenindeling die wordt gebruikt in autorisatieheaders. Met dit token kunt u de communicatie tussen twee systemen op een veilige manier ontwerpen. Laten we JWT herformuleren als het "drager-token" voor de doeleinden van deze tutorial. Een dragertok bestaat uit drie delen: koptekst, payload en handtekening.
U kunt het JWT-schema en een voorbeeld-token hieronder bekijken;
U hoeft de tokengenerator voor dragers niet te implementeren omdat u versies kunt vinden die al in verschillende talen bestaan. Je kunt er enkele hieronder bekijken:
Taal | Bibliotheek-URL |
---|---|
NodeJS | http://github.com/auth0/node-jsonwebtoken |
PHP | http://github.com/firebase/php-jwt |
Java | http://github.com/auth0/java-jwt |
Robijn | http://github.com/progrium/ruby-jwt |
.NETTO | http://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet |
Python | http://github.com/progrium/pyjwt/ |
Na enkele basisinformatie over token-gebaseerde authenticatie te hebben behandeld, kunnen we nu verder gaan met een praktisch voorbeeld. Bekijk het volgende schema, waarna we het in meer detail analyseren:
https://api.yourexampleapp.com
. Als veel mensen de applicatie gebruiken, zijn mogelijk meerdere servers nodig om de gevraagde bewerking te bedienen.https://api.yourexampleapp.com
, eerst zal de load balancer een verzoek afhandelen en vervolgens zal de client naar een specifieke server worden omgeleid.https://api.yourexampleapp.com
, de back-endapplicatie onderschept de verzoekheader en extrakt token-informatie uit de autorisatiekop. Een databasequery wordt gemaakt met behulp van dit token. Als dit token geldig is en de vereiste toestemming heeft voor toegang tot het gevraagde eindpunt, wordt dit voortgezet. Als dit niet het geval is, wordt een 403-antwoordcode geretourneerd (wat een verboden status aangeeft).Op tokens gebaseerde authenticatie biedt verschillende voordelen die ernstige problemen oplossen. Sommigen van hen zijn als volgt:
temp
map, althans voor de eerste keer. Laten we zeggen dat je meerdere servers hebt en dat er een sessie wordt aangemaakt op de eerste server. Wanneer u een nieuw verzoek indient en uw aanvraag op een andere server valt, bestaat er geen sessie-informatie en krijgt u een "ongeoorloofde" reactie. Ik weet het, je kunt dat oplossen met een plakkerige sessie. Bij token-gebaseerde authenticatie wordt deze zaak echter op natuurlijke wijze opgelost. Er is geen probleem met de kleverige sessie, omdat de aanvraagtoken op elk verzoek op elke server wordt onderschept.Dat zijn de meest voorkomende voordelen van token-gebaseerde authenticatie en communicatie. Dat is het einde van de theoretische en architectonische lezing over token-gebaseerde authenticatie. Tijd voor een praktisch voorbeeld.
U ziet twee toepassingen die op tokens gebaseerde verificatie demonstreren:
In het back-end project zullen er service-implementaties zijn en zullen de servaresultaten in JSON-formaat zijn. Er wordt geen mening geretourneerd in services. In het front-end project zal er een AngularJS-project zijn voor front-end HTML en vervolgens zal de front-end-app worden ingevuld door AngularJS-services om verzoeken te doen aan de back-endservices.
In het back-end project zijn er drie hoofdbestanden:
package.json
is voor afhankelijkheidsmanagement.modellen \ user.js
bevat een gebruikersmodel dat zal worden gebruikt voor het maken van databasebewerkingen over gebruikers.server.js
is voor project bootstrapping en behandeling van aanvragen.Dat is het! Dit project is heel eenvoudig, zodat je het hoofdconcept eenvoudig kunt begrijpen zonder een diepe duik te hoeven maken.
"name": "angular-restful-auth", "version": "0.0.1", "dependencies": "express": "4.x", "body-parser": "~ 1.0.0" , "morgan": "latest", "mongoose": "3.8.8", "jsonwebtoken": "0.4.0", "engines": "node": "> = 0.10.0"
package.json
bevat afhankelijkheden voor het project: uitdrukken
voor MVC, body-parser
voor het nabootsen van nabehandeling in NodeJS, morgan
voor aanvraag logging, mangoest
voor ons ORM-framework om verbinding te maken met MongoDB, en jsonwebtoken
voor het maken van JWT-tokens met behulp van ons gebruikersmodel. Er is ook een attribuut genaamd motoren
dat zegt dat dit project is gemaakt met behulp van NodeJS-versie> = 0.10.0. Dit is handig voor PaaS-services zoals Heroku. We zullen dat onderwerp ook in een andere sectie behandelen.
var mongoose = vereisen ('mongoose'); var Schema = mongoose.Scema; var UserSchema = nieuw schema (email: String, wachtwoord: String, token: String); module.exports = mongoose.model ('Gebruiker', UserSchema);
We hebben gezegd dat we een token zouden genereren met behulp van de payload van het gebruikersmodel. Dit model helpt ons om gebruikersbewerkingen op MongoDB uit te voeren. In user.js
, het gebruikersschema is gedefinieerd en het gebruikersmodel is gemaakt met behulp van een mangoest-model. Dit model is gereed voor databasebewerkingen.
Onze afhankelijkheden zijn gedefinieerd en ons gebruikersmodel is gedefinieerd, dus laten we nu al die gebruikers combineren om een service te bouwen voor het afhandelen van specifieke verzoeken.
// Vereiste modules var express = require ("express"); var morgan = require ("morgan"); var bodyParser = require ("body-parser"); var jwt = require ("jsonwebtoken"); var mongoose = vereisen ("mangoest"); var app = express ();
In NodeJS kunt u een module in uw project opnemen met behulp van vereisen
. Eerst moeten we de nodige modules importeren in het project:
var port = process.enTIEPORT || 3001; var User = require ('./ models / User'); // Maak verbinding met DB mongoose.connect (process.env.MONGO_URL);
Onze service zal dienen via een specifieke poort. Als een willekeurige poortvariabele in de systeemomgevingsvariabelen is gedefinieerd, kunt u die gebruiken of hebben we een gedefinieerde poort 3001
. Hierna wordt het gebruikersmodel opgenomen en wordt de databaseverbinding tot stand gebracht om een aantal gebruikersbewerkingen uit te voeren. Vergeet niet om een omgevingsvariabele te definiëren-MONGO_URL
-voor de databaseverbindings-URL.
app.use (bodyParser.urlencoded (extended: true)); app.use (bodyParser.json ()); app.use (morgan ( "dev")); app.use (functie (req, res, next) res.setHeader ('Access-Control-Allow-Origin', '*'); res.setHeader ('Access-Control-Allow-Methods', 'GET, POST '); res.setHeader (' Access-Control-Allow-Headers ',' X-Requested-With, content-type, Authorization '); next (););
In de bovenstaande sectie hebben we enkele configuraties gemaakt voor het simuleren van een HTTP-aanvraagafhandeling in NodeJS met behulp van Express. We staan verzoeken toe om van verschillende domeinen te komen om een klantonafhankelijk systeem te ontwikkelen. Als u dit niet toestaat, activeert u een CORS-fout (Cross Origin Request Sharing) in de webbrowser.
Access-Control-Laat-Origin
toegestaan voor alle domeinen.POST
en KRIJGEN
verzoeken om deze service.X-Gevraagde-With
en inhoudstype
headers zijn toegestaan.app.post ('/ authenticate', function (req, res) User.findOne (email: req.body.email, password: req.body.password, function (err, user) if (err) res.json (type: false, data: "Fout opgetreden:" + err); else if (user) res.json (type: true, data: user, token: user.token); else res.json (type: false, data: "Incorrect email / password");););
We hebben alle vereiste modules geïmporteerd en onze configuratie gedefinieerd, dus nu is het tijd om verzoekverwerkingsprogramma's te definiëren. In de bovenstaande code, telkens wanneer u een POST
verzoek om / authenticeren
met gebruikersnaam en wachtwoord krijgt u een JWT
token. Eerst wordt de databasequery verwerkt met behulp van een gebruikersnaam en wachtwoord. Als er een gebruiker bestaat, worden de gebruikersgegevens met het token geretourneerd. Maar wat als er geen dergelijke gebruiker overeenkomt met de gebruikersnaam en / of het wachtwoord?
app.post ('/ signin', functie (req, res) User.findOne (email: req.body.email, password: req.body.password, function (err, user) if (err) res.json (type: false, data: "Fout opgetreden:" + err); else if (user) res.json (type: false, data: "User already exists!"); else var userModel = new User (); userModel.email = req.body.email; userModel.password = req.body.password; userModel.save (function (err, user) user.token = jwt.sign (gebruiker , process.env.JWT_SECRET); user.save (function (err, user1) res.json (type: true, data: user1, token: user1.token););)); );
Wanneer je een maakt POST
verzoek om /aanmelden
met gebruikersnaam en wachtwoord wordt een nieuwe gebruiker aangemaakt met behulp van geposte gebruikersinformatie. Op de 19e
regel, kunt u zien dat een nieuw JSON-token wordt gegenereerd met behulp van de jsonwebtoken
module, die is toegewezen aan de JWT
variabel. Het authenticatiedeel is OK. Wat als we proberen toegang te krijgen tot een beperkt eindpunt? Hoe kunnen we dit eindpunt bereiken??
app.get ('/ me', sureAuthorized, function (req, res) User.findOne (token: req.token, function (err, user) if (err) res.json (type: false , data: "Fout opgetreden:" + err); else res.json (type: true, data: user);););
Wanneer je een maakt KRIJGEN
verzoek om /me
, u krijgt de huidige gebruikersinformatie, maar om door te gaan met het gevraagde eindpunt, de ensureAuthorized
functie wordt uitgevoerd.
function sureAhororized (req, res, next) var bearerToken; var bearerHeader = req.headers ["authorisatie"]; if (typeof bearerHeader! == 'undefined') var bearer = bearerHeader.split (""); bearerToken = drager [1]; req.token = bearerToken; next (); else res.send (403);
In deze functie worden verzoekkopballen onderschept en de machtiging
header wordt geëxtraheerd. Als er een dragertokje in deze kop bestaat, is dat token toegewezen aan req.token
om gedurende het hele verzoek te worden gebruikt en het verzoek kan worden voortgezet door gebruik te maken van next ()
. Als een token niet bestaat, krijg je een 403 (Verboden) antwoord. Laten we teruggaan naar de handler /me
, en gebruiken req.token
om gebruikersgegevens op te halen met dit token. Wanneer u een nieuwe gebruiker maakt, wordt een token gegenereerd en opgeslagen in het gebruikersmodel in DB. Die tokens zijn uniek.
We hebben slechts drie handlers voor dit eenvoudige project. Daarna zul je zien;
process.on ('uncaughtException', function (err) console.log (err););
De NodeJS-app kan vastlopen als er een fout optreedt. Met de bovenstaande code wordt die crash voorkomen en wordt er een foutenlogboek afgedrukt in de console. En tot slot kunnen we de server starten met behulp van het volgende codefragment.
// Start Server app.listen (poort, functie () console.log ("Express server luisteren op poort" + poort););
Op te sommen:
We zijn klaar met de back-endservice. Zodat het door meerdere clients kan worden gebruikt, kunt u deze eenvoudige servertoepassing op uw servers implementeren of misschien kunt u in Heroku implementeren. Er is een bestand met de naam Procfile
in de hoofdmap van het project. Laten we onze service inzetten in Heroku.
U kunt het back-endproject vanuit deze GitHub-repository klonen.
Ik zal niet bespreken hoe je een app in Heroku kunt maken; je kunt dit artikel raadplegen voor het maken van een Heroku-app als je dit nog niet eerder hebt gedaan. Nadat u uw Heroku-app hebt gemaakt, kunt u een bestemming aan uw huidige project toevoegen met de volgende opdracht:
git remote add heroku
Nu heb je een project gekloond en een bestemming toegevoegd. Na git add
en Git commit
, je kunt je code naar Heroku pushen door te spelen git push heroku master
. Wanneer u een project succesvol pusht, voert Heroku het npm installeren
commando om afhankelijkheden te downloaden naar de temp
map op Heroku. Hierna start het uw toepassing en heeft u toegang tot uw service met behulp van het HTTP-protocol.
In het front-end project ziet u een AngularJS-project. Hier zal ik alleen de hoofdsecties in het front-end project noemen, omdat AngularJS niet iets is dat kan worden behandeld in een enkele tutorial.
Je kunt het project klonen vanuit deze GitHub-repository. In dit project ziet u de volgende mappenstructuur:
ngStorage.js
is een bibliotheek voor AngularJS om lokale opslagbewerkingen te manipuleren. Er is ook een hoofdlay-out index.html
en partials die de hoofdlay-out onder de extensie uitbreiden partials
map. controllers.js
is voor het definiëren van onze controlleracties in de front-end. services.js
is voor het maken van service-aanvragen voor onze service die ik in het vorige project heb genoemd. We hebben een bootstrap-achtig bestand genaamd app.js
en in dit bestand worden configuraties en moduleimporten toegepast. Tenslotte, client.js
is voor het weergeven van statische HTML-bestanden (of gewoon index.html
, in dit geval); dit helpt ons om statische HTML-bestanden weer te geven wanneer u op een server implementeert zonder Apache of andere webservers te gebruiken.
...
In het hoofdlay-out HTML-bestand zijn alle vereiste JavaScript-bestanden opgenomen voor AngularJS-gerelateerde bibliotheken, evenals ons aangepaste controller-, service- en app-bestand.
'gebruik strikt'; / * Controllers * / angular.module ('angularRestfulAuth') .controller ('HomeCtrl', ['$ rootScope', '$ scope', '$ location', '$ localStorage', 'Main', function ($ rootScope, $ scope, $ location, $ localStorage, Main) $ scope.signin = function () var formData = email: $ scope.email, password: $ scope.password Main.signin (formData, function (res) if (res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/";, function () $ rootScope.error = 'Aanmelden mislukt;); $ scope.signup = function () var formData = email: $ scope.email, wachtwoord: $ scope.password Main.save (formData, function (res) if ( res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/", function () $ rootScope.error = 'Mislukt naar signup ';); $ scope.me = function () Main.me (function (res) $ scope.myDetails = res;, function () $ rootScope.error =' Fouten om details op te halen '; ); $ scope.logout = function () Main.logout (fu nction () window.location = "/", function () alert ("Kan niet afmelden!"); ); ; $ scope.token = $ localStorage.token; ])
In de bovenstaande code, de HomeCtrl
controller is gedefinieerd en sommige vereiste modules worden geïnjecteerd zoals $ rootScope
en $ scope
. Injectie van afhankelijkheid is een van de sterkste eigenschappen van AngularJS. $ scope
is de brugvariabele tussen controllers en views in AngularJS, wat betekent dat je kunt gebruiken test
in beeld als je het hebt gedefinieerd in een bepaalde controller zoals $ Scope.test = ...
In deze controller zijn enkele hulpprogramma-functies gedefinieerd, zoals:
aanmelden
om een inlogknop in te stellen op het inlogformulierinschrijven
voor het afhandelen van aanmeldingsformulierenme
voor het toewijzen van de knop Me in de lay-outIn de hoofdlay-out kunt u in de hoofdmenulijst de data-ng-controller
attribuut met een waarde HomeCtrl
. Dat betekent dat dit menu dom
element kan bereik met delen HomeCtrl
. Wanneer u op de aanmeldknop in het formulier klikt, wordt de aanmeldingsfunctie in het controllerbestand uitgevoerd en wordt in deze functie de aanmeldservice gebruikt vanuit de Hoofd
service die al in deze controller is geïnjecteerd.
De hoofdstructuur is weergave -> controller -> service
. Deze service maakt eenvoudige Ajax-verzoeken naar de back-end om specifieke gegevens te verkrijgen.
'gebruik strikt'; angular.module ('angularRestfulAuth') .factory ('Main', ['$ http', '$ localStorage', function ($ http, $ localStorage) var baseUrl = "your_service_url"; function changeUser (user) angular. extend (currentUser, user); function urlBase64Decode (str) var output = str.replace ('-', '+'). replace ('_', '/'); switch (output.length% 4) case 0: break; case 2: output + = '=='; break; case 3: output + = '='; break; standaard: throw 'Illegal base64url string!'; return window.atob (output); function getUserFromToken () var token = $ localStorage.token; var user = ; if (typeof token! == 'undefined') var encoded = token.split ('.') [1]; user = JSON. parseren (urlBase64Decode (gecodeerd)); keer gebruiker terug; var currentUser = getUserFromToken (); return save: function (data, success, error) $ http.post (baseUrl + '/ signin', data) .success ( success) .error (error), signin: function (data, succes, error) $ http.post (baseUrl + '/ authenticate', data) .success (succes) .error (error), me: function ( succes, fout) $ htt p.get (baseUrl + '/me').success(success).error(error), logout: function (succes) changeUser (); delete $ localStorage.token; succes(); ; ]);
In de bovenstaande code ziet u servicefuncties zoals het aanvragen van verificatie. In controller.js realiseerde je je misschien al dat er functies zijn zoals Main.me
. Deze Hoofd
service is geïnjecteerd in de controller en in de controller worden de services die bij deze service horen rechtstreeks gebeld.
Deze functies zijn eenvoudig Ajax-verzoeken aan onze service die we samen hebben ingezet. Vergeet niet om de service-URL in te voegen baseurl
in de bovenstaande code. Wanneer u uw service naar Heroku implementeert, krijgt u een service-URL zoals appname.herokuapp.com
. In de bovenstaande code, stelt u in var baseUrl = "appname.herokuapp.com"
.
In het aanmeldings- of aanmeldingsgedeelte van de toepassing reageert het drager-token op het verzoek en wordt dit token opgeslagen in de lokale opslag. Wanneer u in de backend een verzoek indient bij een service, moet u dit token in de headers plaatsen. U kunt dit doen met behulp van AngularJS-interceptors.
$ httpProvider.interceptors.push (['$ q', '$ location', '$ localStorage', function ($ q, $ location, $ localStorage) return 'request': function (config) config.headers = config.headers || ; if ($ localStorage.token) config.headers.Authorization = 'Bearer' + $ localStorage.token; return config;, 'responseError': function (response) if (response. status === 401 || response.status === 403) $ location.path ('/ signin'); return $ q.reject (response);;]);
In de bovenstaande code wordt elk verzoek onderschept en worden een autorisatiekop en -waarde in de headers geplaatst.
In het front-end project, hebben we enkele gedeeltelijke pagina's zoals aanmelden
, inschrijven
, profiel details
, en vb
. Deze gedeeltelijke pagina's hebben betrekking op specifieke controllers. Je kunt die relatie zien in app.js
:
angular.module ('angularRestfulAuth', ['ngStorage', 'ngRoute']) .config (['$ routeProvider', '$ httpProvider', function ($ routeProvider, $ httpProvider) $ routeProvider. when ('/', templateUrl: 'partials / home.html', controller: 'HomeCtrl'). when ('/ signin', templateUrl: 'partials / signin.html', controller: 'HomeCtrl'). when ('/ aanmelden ', templateUrl:' partials / signup.html ', controller:' HomeCtrl '). when (' / me ', templateUrl:' partials / me.html ', controller:' HomeCtrl '). anders ( omleiden naar: '/' );
Zoals je gemakkelijk kunt begrijpen in de bovenstaande code, wanneer je naar /
, de home.html
pagina wordt weergegeven. Nog een voorbeeld: als je gaat /inschrijven
, signup.html
wordt weergegeven. Deze renderingbewerking zal in de browser worden gedaan, niet aan de serverzijde.
Je kunt zien hoe alles wat we in deze tutorial hebben besproken, in de praktijk wordt omgezet door deze werkende demo te bekijken.
Op token gebaseerde authenticatiesystemen helpen u bij het bouwen van een authenticatie- / autorisatiesysteem terwijl u klantonafhankelijke services ontwikkelt. Door deze technologie te gebruiken, richt u zich alleen op uw diensten (of API's).
Het authenticatie- / autorisatiegedeelte wordt behandeld door het token-gebaseerde authenticatiesysteem als een laag voor uw services. U kunt toegang krijgen tot en gebruik maken van services van elke client, zoals webbrowsers, Android, iOS of een desktopclient.
En als u op zoek bent naar kant-en-klare oplossingen, bekijk dan de authenticatiescripts en apps op Envato Market.