赞
踩
jenkins 构建api
Many modern applications separate the backend services from the frontend user interface. In this type of architecture, the backend will expose a web based API that the frontend client consumes. Typically, the backend will handle incoming requests and return a JSON or XML encoded response. The frontend will then be in charge of formatting, styling, and displaying this response to the user. We’ve all heard the term “separation of concerns” and applying it at this scale has many benefits.
许多现代应用程序将后端服务与前端用户界面分开。 在这种类型的体系结构中,后端将公开前端客户端使用的基于Web的API。 通常,后端将处理传入的请求并返回JSON或XML编码的响应。 然后,前端将负责格式化,样式设置以及向用户显示此响应。 我们都听说过“关注点分离”一词,并在此规模上应用有很多好处。
The backend services can grow and evolve independent of the frontend client. New APIs, if properly versioned, can provide new features and functionality without breaking existing integrations. A single backend can interface with multiple clients and the frontend clients will not be limited to any specific framework or programming language. This means that your single backend can work with your app for both a web based implementation, your mobile app, and even with IoT devices.
后端服务可以独立于前端客户端而增长和发展。 如果版本正确,新的API可以提供新的特性和功能,而不会破坏现有的集成。 单个后端可以与多个客户端进行交互,并且前端客户端将不限于任何特定的框架或编程语言。 这意味着您的单个后端可以与您的应用程序一起用于基于Web的实施,您的移动应用程序甚至是IoT设备。
In today’s post, we are going to build and expose a RESTful API built with NodeJS. We are also going to build two front-end clients, also with NodeJS, that are going to consume our API. Our API is going to be protected with Auth0. In addition to protecting our API endpoints, we’ll also add specific roles to each of our clients and show how we can give granular access to our API.
在今天的帖子中,我们将构建并公开使用NodeJS构建的RESTful API。 我们还将使用NodeJS构建两个前端客户端,它们将使用我们的API。 我们的API将通过Auth0保护。 除了保护我们的API端点之外,我们还将为每个客户添加特定的角色,并展示如何为我们的API提供精细的访问权限。
Let’s get started.
让我们开始吧。
The application we are building today will be an app called Movie Analyst. Movie Analyst aggregates and displays a list of the latest movie reviews by famous critics across the web. Users can view the latest movie reviews, learn about the critics that review them, and also see the publications that Movie Analyst has partnered with. Website administrators, who will have a separate website, can see and edit these pages, but also have access to reviews-in-progress so that they can prepare and approve reviews ahead of time.
我们今天正在构建的应用程序将是名为Movie Analyst的应用程序。 Movie Analyst汇总并显示网络上著名评论家的最新电影评论列表。 用户可以查看最新的电影评论,了解评论他们的评论家,还可以查看Movie Analyst与之合作的出版物。 网站管理员将拥有一个单独的网站,他们可以查看和编辑这些页面,还可以访问进行中的评论,以便他们可以提前准备和批准评论。
Rather than building two separate backends for the user facing side and the admin application, we are going to build and expose an API with four endpoints. The user facing app will have access to the movies, reviewers and publications endpoints, while the admin app will additionally have access to the pending endpoint which will return movie reviews that are not ready to be publically accessible. We’ll start by building our API.
与其为面向用户的一侧和管理应用程序构建两个单独的后端,不如构建和公开具有四个端点的API。 面向用户的应用程序将有权访问电影,审阅者和出版物端点,而管理应用程序将另外具有对未决端点的访问权,这些端点将返回尚未准备好公开访问的电影评论。 我们将从构建API开始。
We’ll build our backend API with NodeJS and the Express framework. To get started let’s create a directory called movie-analyst-api
, navigate into it and run npm init
to setup our package.json
file. You can leave all the default settings, or change them as you see fit. Once you’ve gone through the setup, the package.json
file will be created in your directory.
我们将使用NodeJS和Express框架构建后端API。 首先,我们创建一个名为movie-analyst-api
的目录,进入该目录并运行npm init
来设置我们的package.json
文件。 您可以保留所有默认设置,也可以根据需要更改它们。 完成设置后,将在目录中创建package.json
文件。
Before we get to writing code, let’s take care of our dependencies. Run npm install express express-jwt auth0-api-jwt-rsa-validation --save
to install the dependencies we are going to need. The express
dependency will pull down the express framework, express-jwt
library will give us functions to work with JSON Web Tokens, and finally auth0-api-jwt-rsa-validation
will provide a helper function for getting our secret key.
在开始编写代码之前,让我们先照顾一下我们的依赖关系。 运行npm install express express-jwt auth0-api-jwt-rsa-validation --save
以安装我们将需要的依赖项。 express
依赖关系将拉低express框架, express-jwt
库将为我们提供使用JSON Web令牌的功能,最后auth0-api-jwt-rsa-validation
将提供帮助函数以获取我们的密钥。
Now that we have our dependencies in place, let’s create a new file called server.js
. Since our backend API is only going to expose a few routes, we’ll write all of our code in this single file. Check out our implementation below.
现在我们有了依赖项,让我们创建一个名为server.js
的新文件。 由于我们的后端API仅会公开一些路由,因此我们将所有代码写入此单个文件中。 在下面查看我们的实施。
- // Get our dependencies
- var express = require('express');
- var app = express();
- var jwt = require('express-jwt');
- var rsaValidation = require('auth0-api-jwt-rsa-validation');
-
- // Implement the movies API endpoint
- app.get('/movies', function(req, res){
- // Get a list of movies and their review scores
- var movies = [
- {title : 'Suicide Squad', release: '2016', score: 8, reviewer: 'Robert Smith', publication : 'The Daily Reviewer'},
- {title : 'Batman vs. Superman', release : '2016', score: 6, reviewer: 'Chris Harris', publication : 'International Movie Critic'},
- {title : 'Captain America: Civil War', release: '2016', score: 9, reviewer: 'Janet Garcia', publication : 'MoviesNow'},
- {title : 'Deadpool', release: '2016', score: 9, reviewer: 'Andrew West', publication : 'MyNextReview'},
- {title : 'Avengers: Age of Ultron', release : '2015', score: 7, reviewer: 'Mindy Lee', publication: 'Movies n\' Games'},
- {title : 'Ant-Man', release: '2015', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
- {title : 'Guardians of the Galaxy', release : '2014', score: 10, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'},
- ]
-
- // Send the response as a JSON array
- res.json(movies);
- })
-
- // Implement the reviewers API endpoint
- app.get('/reviewers', function(req, res){
- // Get a list of all of our reviewers
- var authors = [
- {name : 'Robert Smith', publication : 'The Daily Reviewer', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/angelcolberg/128.jpg'},
- {name: 'Chris Harris', publication : 'International Movie Critic', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/bungiwan/128.jpg'},
- {name: 'Janet Garcia', publication : 'MoviesNow', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/grrr_nl/128.jpg'},
- {name: 'Andrew West', publication : 'MyNextReview', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/d00maz/128.jpg'},
- {name: 'Mindy Lee', publication: 'Movies n\' Games', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/laurengray/128.jpg'},
- {name: 'Martin Thomas', publication : 'TheOne', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/karsh/128.jpg'},
- {name: 'Anthony Miller', publication : 'ComicBookHero.com', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/9lessons/128.jpg'}
- ];
-
- // Send the list of reviewers as a JSON array
- res.json(authors);
- })
-
- // Implement the publications API endpoint
- app.get('/publications', function(req, res){
- // Get a list of publications
- var publications = [
- {name : 'The Daily Reviewer', avatar: 'glyphicon-eye-open'},
- {name : 'International Movie Critic', avatar: 'glyphicon-fire'},
- {name : 'MoviesNow', avatar: 'glyphicon-time'},
- {name : 'MyNextReview', avatar: 'glyphicon-record'},
- {name : 'Movies n\' Games', avatar: 'glyphicon-heart-empty'},
- {name : 'TheOne', avatar : 'glyphicon-globe'},
- {name : 'ComicBookHero.com', avatar : 'glyphicon-flash'}
- ];
-
- // Send the list of publications as a JSON array
- res.json(publications);
- })
-
- // Implement the pending reviews API endpoint
- app.get('/pending', function(req, res){
- // Get a list of pending movie reviews
- var pending = [
- {title : 'Superman: Homecoming', release: '2017', score: 10, reviewer: 'Chris Harris', publication: 'International Movie Critic'},
- {title : 'Wonder Woman', release: '2017', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
- {title : 'Doctor Strange', release : '2016', score: 7, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'}
- ]
-
- // Send the list of pending movie reviews as a JSON array
- res.send(pending);
- })
-
- // Launch our API Server and have it listen on port 8080.
- app.listen(8080);
If we launch our API server and navigate to any of the four routes we’ve created, we should get the expected response. Let’s test this by running node server
and navigating to localhost:8080/movies
. You should see a JSON response with the list of movie reviews and their associated data.
如果我们启动API服务器并导航到我们创建的四个路由中的任何一个,我们都应该得到预期的响应。 让我们通过运行node server
并导航到localhost:8080/movies
。 您应该看到一个带有电影评论及其相关数据列表的JSON响应。
We have our API but now anyone can call it. Let’s fix this by securing our endpoints with Auth0. Auth0 makes user identity simple and recently they released a new feature to make securing API endpoints a breeze as well. If you don’t already have an Auth0 account, signup for a free account here. Once you’ve signed up, navigate to your Auth0 dashboard and click on the New Client button to create a new Auth0 application.
我们有我们的API,但现在任何人都可以调用它。 让我们通过使用Auth0保护端点来解决此问题 。 Auth0使用户身份变得简单,最近他们发布了一项新功能,以使保护API端点也变得轻而易举。 如果您还没有Auth0帐户,请在此处注册一个免费帐户。 注册后,导航到Auth0 仪表板 ,然后单击New Client按钮以创建新的Auth0应用程序。
For the client type, select Non-Interactive Client and name it MovieAnalyst Website. Click on the Create button to create the client.
对于客户端类型,选择“ 非交互式客户端”并将其命名为MovieAnalyst网站。 单击创建按钮以创建客户端。
As this is a new feature that is still in beta, you will be prompted to enable the API functionality. You can do so by simply flipping the switch and a new menu option titled “API’ will be available. Click on this new menu option to continue.
由于这是尚处于测试阶段的新功能,因此将提示您启用API功能。 您可以通过简单地拨动开关来做到这一点,然后会出现一个名为“ API”的新菜单选项。 单击此新菜单选项以继续。
The API tab will already have one API created automatically, this is the Auth0 Management API. Utilizing this API, you can create your own Auth0 dashboard or manage any of the Auth0 features programmatically. To learn more about the features of the Management API, click here. We won’t be using this API, so let’s instead click on the Create API button to create our own API.
“ API”标签将已经自动创建了一个API,即Auth0管理API。 利用此API,您可以创建自己的Auth0仪表板或以编程方式管理任何Auth0功能。 要了解有关Management API功能的更多信息, 请单击此处 。 我们不会使用此API,因此让我们单击“ 创建API”按钮来创建我们自己的API。
You can name your API whatever you want, we’ve chosen MovieAnalystAPI. The identifier field will be a unique identifier for your API. This field does not have to be a URL, so feel free to put an identifier that makes the most sense to you - just know that this field cannot be changed. We’ll leave the signing algorithm as RS256 and click the Create API button.
您可以随意命名API,我们选择了MovieAnalystAPI。 标识符字段将是您的API的唯一标识符。 该字段不必是URL,因此请随意添加一个对您最有意义的标识符-只需知道该字段不能更改即可。 我们将签名算法保留为RS256,然后单击“创建API”按钮。
We are also going to create one more client for our Admin front end. This will follow the same process as when we created the first client. Navigate to the Clients section and click on Create New Client. This time name the client MovieAnalyst Admin and make it a non-interactive client as well. Click on the Create button to create this client.
我们还将为我们的管理前端再创建一个客户端。 这将与创建第一个客户端时的过程相同。 导航到“ 客户端”部分,然后单击“ 创建新客户端” 。 这次将客户端命名为MovieAnalyst Admin,并将其也设为非交互式客户端。 单击创建按钮创建此客户端。
With our two clients and API created in the Auth0 dashboard, we can now go ahead and secure our API endpoints. Open up the server.js
file again and make the following updates.
通过在Auth0信息中心中创建了两个客户端和API,我们现在可以继续保护我们的API端点。 再次打开server.js
文件,并进行以下更新。
- // dependencies omitted
-
- // We’ll create a middleware function to validate the access token when our API is called
- // Note that the audience field is the identifier you gave to your API.
- var jwtCheck = jwt({
- secret: rsaValidation(),
- algorithms: ['RS256'],
- issuer: "https://YOUR-AUTH0-DOMAIN.auth0.com/",
- audience: 'https://movieanalyst.com'
- });
-
- // Enable the use of the jwtCheck middleware in all of our routes
- app.use(jwtCheck);
-
- // If we do not get the correct credentials, we’ll return an appropriate message
- app.use(function (err, req, res, next) {
- if (err.name === 'UnauthorizedError') {
- res.status(401).json({message:'Missing or invalid token'});
- }
- });
-
- // routes omitted
If we launch our server now and navigate to any of the routes, we’ll get an error message as we are not providing the correct information on our requests to the server.
如果我们现在启动服务器并导航到任何路线,则会收到错误消息,因为我们没有向服务器提供有关请求的正确信息。
This is intended as we don’t want just anyone to have access to our API endpoints. In addition to protecting our API routes, we are also going to fine-tune access to specific endpoints. Luckily, we can do this with ease in the Auth0 management dashboard.
这是因为我们不希望任何人都可以访问我们的API端点。 除了保护我们的API路由外,我们还将微调对特定端点的访问。 幸运的是,我们可以在Auth0 管理仪表板中轻松完成此操作。
Head over the management dashboard, navigate to the API section and click into the MovieAnalyst API we created earlier. From here, click on the Scopes tab.
转到管理仪表板 ,导航到“ API”部分,然后单击我们之前创建的MovieAnalyst API。 在这里,单击“ 作用域”选项卡。
Scopes allow us to grant specific permissions to clients that are authorized to use our API. For our demo, we are going to create two scopes, general and admin. In an actual application, you could further narrow down the scopes by giving read or write permissions and even go as far to protect each individual route with a separate scope. We’ll keep it fairly general here though. Go ahead and create the two scopes now.
范围使我们可以向被授权使用我们的API的客户端授予特定权限。 对于我们的演示,我们将创建两个范围,常规和管理。 在实际的应用程序中,您可以通过授予读取或写入权限来进一步缩小范围,甚至可以通过单独的范围来保护每个单独的路由。 不过,我们在这里将其保留得相当笼统。 继续并立即创建两个范围。
Now that we have our scopes in place, the last thing we’ll need to do is authorize our two clients to work with the API we created. Within the MovieAnalystAPI section, navigate to the Non-Interactive Clients tab. Here you will see a list of all the clients that can interface with our API. By default, when we created our MovieAnalystAPI a test client was created for us. We’ll also see the two clients we created but they will be displayed as Unauthorized.
现在我们已经有了作用域,我们需要做的最后一件事就是授权我们的两个客户端使用我们创建的API。 在MovieAnalystAPI部分中,导航到“非交互式客户端”选项卡。 在这里,您将看到可以与我们的API交互的所有客户端的列表。 默认情况下,当我们创建MovieAnalystAPI时,会为我们创建一个测试客户端。 我们还将看到我们创建的两个客户端,但它们将显示为“未经授权”。
To authorize our clients, flip the switch to Authorized and you will see additional information displayed. In addition to enabling access to the client, we can easily manage which scopes the client will have. For the admin client, let’s enable both the general and admin scopes, and for the website client, we’ll just enable general access. Be sure to hit the Update button once you’ve made the scope changes.
要授权我们的客户,将开关拨到“已授权”,您将看到显示的其他信息。 除了启用对客户端的访问之外,我们还可以轻松管理客户端将具有的范围。 对于admin客户端,让我们启用常规和管理范围,对于网站客户端,我们仅启用常规访问。 更改范围后,请务必点击“ 更新”按钮。
Now that we have our scopes in place, let’s go back to the server.js
file for our API to make some final edits. What we are adding now is the ability to check if the client has permissions to view the endpoint requested. To do this, we’ll create another middleware that will look at the decoded JWT and see if it the token has the correct scope. If it doesn’t we’ll send an appropriate forbidden message, otherwise we’ll send the data. Take a look at our implementation of this functionality below.
现在我们已经有了作用域,让我们回到我们的API的server.js
文件中进行一些最终编辑。 我们现在添加的功能是能够检查客户端是否具有查看请求的端点的权限。 为此,我们将创建另一个中间件,该中间件将查看已解码的JWT,并查看令牌是否具有正确的作用域。 如果没有,我们将发送适当的禁止消息,否则我们将发送数据。 在下面查看我们对该功能的实现。
- // dependencies
-
- // existing jwtCheck middleware
-
- var guard = function(req, res, next){
- // we’ll use a case switch statement on the route requested
- switch(req.path){
- // if the request is for movie reviews we’ll check to see if the token has general scope
- case '/movies' : {
- var permissions = ['general'];
- for(var i = 0; i < permissions.length; i++){
- if(req.user.scope.includes(permissions[i])){
- next();
- } else {
- res.send(403, {message:'Forbidden'});
- }
- }
- break;
- }
- // Same for the reviewers
- case '/reviewers': {
- var permissions = ['general'];
- for(var i = 0; i < permissions.length; i++){
- if(req.user.scope.includes(permissions[i])){
- next();
- } else {
- res.send(403, {message:'Forbidden'});
- }
- }
- break;
- }
- // Same for publications
- case '/publications': {
- var permissions = ['general'];
- for(var i = 0; i < permissions.length; i++){
- if(req.user.scope.includes(permissions[i])){
- next();
- } else {
- res.send(403, {message:'Forbidden'});
- }
- }
- break;
- }
- // For the pending route, we’ll check to make sure the token has the scope of admin before returning the results.
- case '/pending': {
- var permissions = ['admin'];
- console.log(req.user.scope);
- for(var i = 0; i < permissions.length; i++){
- if(req.user.scope.includes(permissions[i])){
- next();
- } else {
- res.send(403, {message:'Forbidden'});
- }
- }
- break;
- }
- }
-
- // existing app.use middleware
-
- app.use(guard);
-
- // routes
Our guard
middleware will be called on each request and will ensure that the token has the correct scope. If it does, we’ll send the data, otherwise we’ll return a 403 Forbidden
status and appropriate message.
我们的guard
中间件将在每次请求时被调用,并确保令牌具有正确的范围。 如果是这样,我们将发送数据,否则我们将返回403 Forbidden
状态和适当的消息。
This wraps up our API implementation. If we launch our server now and try to navigate to any of the routes, we’ll still see the 401 Unauthenticated response. That’s still ok, as our API is meant to interface with other clients and not end-user browsers. Next, we’ll implement our first client, the MovieAnalyst Website.
这总结了我们的API实现。 如果我们现在启动服务器并尝试导航到任何路由,我们仍将看到401未经身份验证的响应。 没关系,因为我们的API旨在与其他客户端而非最终用户浏览器进行交互。 接下来,我们将实现我们的第一个客户端MovieAnalyst网站。
We have our API ready, now let’s build a UI to consume it. Create a new directory titled movieanalyst-website
and navigate into it. Run npm init
for this directory to create a package.json
file just like for the API. For our website dependencies run npm install express ejs superagent --save
. The express
dependency is self-explanatory, we will use ejs
as our templating engine and finally the superagent
library will handle making HTTP requests to our API.
我们已经准备好API,现在让我们构建一个UI来使用它。 创建一个名为movieanalyst-website
的新目录并导航到该目录。 与该API一样,对该目录运行npm init
来创建package.json
文件。 对于我们的网站依赖项,请运行npm install express ejs superagent --save
。 express
依赖关系是不言自明的,我们将使用ejs
作为模板引擎,最后, superagent
库将处理向我们的API发出HTTP请求。
In addition to creating a server.js
file, you’ll also want to create a public
directory and in this directory, create a subdirectory titled views
. We are going to have four views for our user facing website so let’s create them now. The four views we are going to create are:
除了创建server.js
文件之外,您还需要创建一个public
目录,并在该目录中创建一个名为views
的子目录。 对于面向用户的网站,我们将有四个视图,所以现在就创建它们。 我们将要创建的四个视图是:
index.ejs
- this will be our homepage index.ejs
这将是我们的主页 movies.ejs
- this will be the view that display movie reviews movies.ejs
这将是显示电影评论的视图 authors.ejs
- this will display the list of critics authors.ejs
这将显示评论者列表 publications.ejs
- this will will display our publication partners publications.ejs
这将显示我们的出版物合作伙伴 With the scaffolding done, open up the server.js
file to get started with the implementation. Let’s take a look at the implementation and we’ll explain what’s going on line by line.
完成脚手架后,打开server.js
文件以开始实施。 让我们看一下实现,然后逐行解释。
- // Declare our dependencies
- var express = require('express');
- var request = require('superagent');
-
- // Create our express app
- var app = express();
-
- // Set the view engine to use EJS as well as set the default views directory
- app.set('view engine', 'ejs');
- app.set('views', __dirname + '/public/views/');
-
- // This tells Express out of which directory to serve static assets like CSS and images
- app.use(express.static(__dirname + '/public'));
-
- // These two variables we’ll get from our Auth0 MovieAnalyst-Website Client.
- // Head over the the management dashboard at https://manage.auth0.com
- // Find the MovieAnalyst Website Client and copy and paste the Client ID and Secret
- var NON_INTERACTIVE_CLIENT_ID = 'YOUR-AUTH0-CLIENT-ID';
- var NON_INTERACTIVE_CLIENT_SECRET = 'YOUR-AUTH0-CLIENT-SECRET';
-
- // Next, we’ll define an object that we’ll use to exchange our credentials for an access token.
- var authData = {
- client_id: NON_INTERACTIVE_CLIENT_ID,
- client_secret: NON_INTERACTIVE_CLIENT_SECRET,
- grant_type: 'client_credentials',
- audience: 'https://movieanalyst.com'
- }
-
- // We’ll create a middleware to make a request to the oauth/token Auth0 API with our authData we created earlier.
- // Our data will be validated and if everything is correct, we’ll get back an access token.
- // We’ll store this token in the req.access_token variable and continue the request execution.
- // It may be repetitive to call this endpoint each time and not very performant, so you can cache the access_token once it is received.
- function getAccessToken(req, res, next){
- request
- .post('https://YOUR-AUTH0-DOMAIN.auth0.com/oauth/token')
- .send(authData)
- .end(function(err, res) {
- if(req.body.access_token){
- req.access_token = res.body.access_token;
- next();
- } else {
- res.send(401, ‘Unauthorized’);
- }
- })
- }
-
- // The homepage route of our application does not interface with the MovieAnalyst API and is always accessible. We won’t use the getAccessToken middleware here. We’ll simply render the index.ejs view.
- app.get('/', function(req, res){
- res.render('index');
- })
-
- // For the movies route, we’ll call the getAccessToken middleware to ensure we have an access token. If we do have a valid access_token, we’ll make a request with the superagent library and we’ll be sure to add our access_token in an Authorization header before making the request to our API.
- // Once the request is sent out, our API will validate that the access_token has the right scope to request the /movies resource and if it does, will return the movie data. We’ll take this movie data, and pass it alongside our movies.ejs template for rendering
- app.get('/movies', getAccessToken, function(req, res){
- request
- .get('http://localhost:8080/movies')
- .set('Authorization', 'Bearer ' + req.access_token)
- .end(function(err, data) {
- if(data.status == 403){
- res.send(403, '403 Forbidden');
- } else {
- var movies = data.body;
- res.render('movies', { movies: movies} );
- }
- })
- })
-
- // The process will be the same for the remaining routes. We’ll make sure to get the acess_token first and then make the request to our API to get the data.
- // The key difference on the authors route, is that for our client, we’re naming the route /authors, but our API endpoint is /reviewers. Our route on the client does not have to match the API endpoint route.
- app.get('/authors', getAccessToken, function(req, res){
- request
- .get('http://localhost:8080/reviewers')
- .set('Authorization', 'Bearer ' + req.access_token)
- .end(function(err, data) {
- if(data.status == 403){
- res.send(403, '403 Forbidden');
- } else {
- var authors = data.body;
- res.render('authors', {authors : authors});
- }
- })
- })
-
- app.get('/publications', getAccessToken, function(req, res){
- request
- .get('http://localhost:8080/publications')
- .set('Authorization', 'Bearer ' + req.access_token)
- .end(function(err, data) {
- if(data.status == 403){
- res.send(403, '403 Forbidden');
- } else {
- var publications = data.body;
- res.render('publications', {publications : publications});
- }
- })
- })
-
- // We’ve added the pending route, but calling this route from the MovieAnalyst Website will always result in a 403 Forbidden error as this client does not have the admin scope required to get the data.
- app.get('/pending', getAccessToken, function(req, res){
- request
- .get('http://localhost:8080/pending')
- .set('Authorization', 'Bearer ' + req.access_token)
- .end(function(err, data) {
- if(data.status == 403){
- res.send(403, '403 Forbidden');
- }
- })
- })
-
- // Our MovieAnalyst Website will listen on port 3000. Feel free to change this as you see fit, just know that you can’t have multiple processes listening on the same port.
- app.listen(3000);
We have our server.js
implementation complete. Let’s build out our UI pages. We’ll start with the index.ejs
page. We will make use of the Bootstrap and Font Awesome libraries to create a good looking UI fast. The homepage will just display links to the other sections of the website. Take a look at the implementation and screenshot of what the completed product will look like.
我们已经完成了server.js
实现。 让我们构建我们的UI页面。 我们将从index.ejs
页面开始。 我们将利用Bootstrap和Font Awesome库快速创建美观的UI。 主页只会显示指向网站其他部分的链接。 查看完整产品外观的实现和屏幕截图。
- <!doctype html>
- <html>
- <head>
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
- <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
- <style>
- body {
- background-color: #ecedec;
- }
- </style>
- </head>
- <body>
-
- <div class="container">
- <div class="jumbotron text-center">
- <h1><span class="glyphicon glyphicon-bullhorn"></span></h1>
- <h2>Movie Analyst</h2>
- </div>
-
- <div class="row">
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title">Latest Reviews</h3>
- </div>
- <div class="panel-body text-center">
- <h1><span class="glyphicon glyphicon-bullhorn"></span></h1>
- </div>
- <div class="panel-footer small">
- <a href="/movies" class="btn btn-block btn-primary">View Latest Reviews</a>
- </div>
- </div>
- </div>
-
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title">View Latest Reviews</h3>
- </div>
- <div class="panel-body text-center">
- <h1><span class="glyphicon glyphicon-user"></span></h1>
- </div>
- <div class="panel-footer small">
- <a href="/authors" class="btn btn-block btn-primary">View Authors</a>
- </div>
- </div>
- </div>
-
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title">Our Partners</h3>
- </div>
- <div class="panel-body text-center">
- <h1><span class="glyphicon glyphicon-tower"></span></h1>
- </div>
- <div class="panel-footer small">
- <a href="/publications" class="btn btn-block btn-primary">View Publications</a>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- </body>
- </html>
Next, we’ll build out the movies.ejs
view. In the interest of time, we’ll omit some the head section. Feel free to copy and paste the contents from the index.ejs
file to save some time.
接下来,我们将构建movies.ejs
视图。 为了节省时间,我们将省略一些头部部分。 随意复制和粘贴index.ejs
文件中的内容以节省时间。
- <div class="row">
- <!-- This is the EJS syntax to create a loop -->
- <% movies.forEach(function(movie, index) { %>
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title"><%= movie.title %> (<%= movie.release %>)</h3>
- </div>
- <div class="panel-body text-center">
- <h1><%= movie.score %> <small>/ 10</small></h1>
- </div>
- <div class="panel-footer small">
- <p>By <%= movie.reviewer %> for <%= movie.publication %>
- </div>
- </div>
- </div>
- <!-- We’ll also have to close our loop -->
- <% }) %>
- </div>
The authors page will look similar as well. The implementation for authors.ejs
is below.
作者页面也会看起来相似。 authors.ejs
的实现如下。
- <div class="container">
- <div class="jumbotron text-center">
- <h1><span class="glyphicon glyphicon-user"></span></h1>
- <h2>Our Movie Critics</h2>
- </div>
-
- <div class="row">
- <% authors.forEach(function(author, index) { %>
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title"><%= author.name %></h3>
- </div>
- <div class="panel-body text-center">
- <img class="img-circle" src="<%= author.avatar %>" />
- </div>
- <div class="panel-footer small">
- <p>Writes for <%= author.publication %>
- </div>
- </div>
- </div>
- <% }) %>
- </div>
- </div>
Finally, the publications.ejs
implementation is below.
最后,下面是publications.ejs
实现。
- <div class="container">
- <div class="jumbotron text-center">
- <h1><span class="glyphicon glyphicon-tower"></span></h1>
- <h2>Our Publication Partners</h2>
- </div>
-
- <div class="row">
- <% publications.forEach(function(publication, index) { %>
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title"><%= publication.name %></h3>
- </div>
- <div class="panel-body text-center">
- <h1><span class="glyphicon <%= publication.avatar %>"></span></h1>
- </div>
- </div>
- </div>
- <% }) %>
- </div>
- </div>
Let’s test our app and make sure that it works. Launch the API server by running the node server
command from the movieanalyst-api directory. Launch the Website server by running the node server
command from the movieanalyst-website directory. Now you will have two node applications running, the API at localhost:8080
and the client facing website at localhost:3000
.
让我们测试一下我们的应用程序,并确保它可以运行。 通过运行来自movieanalyst-api目录的node server
命令来启动API服务器。 通过从movieanalyst-website目录运行node server
命令来启动网站服务器。 现在,您将运行两个节点应用程序,API在localhost:8080
和面向客户端的网站在localhost:3000
。
Navigate to localhost:3000
and you should see the homepage we’ve designed. Click on any of the links, and you should see the appropriate page displayed with the data we’ve defined in our API backend project. Try navigating to localhost:3000/pending
. Remember, we created this route, but the response will be forbidden as our client does not have the admin
scope applied to it. Let’s build our Admin frontend that will have this scope.
导航到localhost:3000
,您应该会看到我们设计的主页。 单击任何链接,您应该会看到相应的页面,其中显示了我们在API后端项目中定义的数据。 尝试导航到localhost:3000/pending
。 记住,我们创建了此路由,但是由于我们的客户端未对其应用admin
范围,因此将禁止响应。 让我们构建具有此作用域的管理前端。
Our admin frontend will be very similar to the user centric frontend. In fact, to get started quickly, let’s just copy and paste the entire directory and rename it movieanalyst-admin
.
我们的管理员前端将与以用户为中心的前端非常相似。 实际上,为了快速入门,我们只需要复制并粘贴整个目录并将其重命名为movieanalyst-admin
。
Let’s open up the server.js
file next as we’ll need to make some updates. We are going to need to update the NON_INTERACTIVE_CLIENT_ID
and NON_INTERACTIVE_CLIENT_SECRET
variables with the values from our MovieAnalyst Admin client. Head over to the management dashboard and get those values and replace them here. The other change we will need to make is to change the port the app will listen on. Since our user website listens on 3000
and our API is on 8080
, we’ll set the app.listen()
port for our admin interface to 4000
.
接下来,我们将打开server.js
文件,因为我们需要进行一些更新。 我们将需要使用MovieAnalyst Admin客户端中的值更新NON_INTERACTIVE_CLIENT_ID
和NON_INTERACTIVE_CLIENT_SECRET
变量。 转至管理仪表板并获取这些值,然后在此处替换它们。 我们需要进行的另一项更改是更改应用程序将监听的端口。 由于我们的用户网站监听3000
而我们的API监听8080
,因此我们将管理界面的app.listen()
端口设置为4000
。
For our UI, we are going to change it up a bit. Since this is an admin based website, rather than just displaying the content, we’ll make it all editable. We won’t actually save changes. Another new feature with the admin website is that we are going to write a partial view for the main navigational menu. Let’s create this partial view first. Create a new subdirectory in your public/views
directory and name it includes
. We’ll call our partial view navbar.ejs
. Create the file and open it. Our navbar implementation will be as follows:
对于我们的UI,我们将对其进行一些更改。 由于这是一个基于管理员的网站,而不仅仅是显示内容,因此我们将使其全部可编辑。 我们实际上不会保存更改 。 管理网站的另一个新功能是,我们将为主导航菜单编写局部视图。 让我们首先创建此局部视图。 在您的public/views
目录中创建一个新的子目录,并将其命名为includes
。 我们将其称为局部视图navbar.ejs
。 创建文件并打开它。 我们的导航栏实现如下:
- <nav class="navbar navbar-inverse">
- <div class="container-fluid">
- <div class="navbar-header">
- <a class="navbar-brand" href="#">Movie Analyst Admin</a>
- </div>
- <div class="collapse navbar-collapse">
- <ul class="nav navbar-nav">
- <li><a href="/movies">Reviews</a></li>
- <li><a href="/authors">Authors</a></li>
- <li><a href="/publications">Publications</a></li>
- <li><a href="/pending">Pending Reviews</a></li>
- </ul>
- </div>
- </div>
- </nav>
Next, we’ll update each of the views to display a form rather than just the content. We’ll also add a save button, so if an admin makes any updates they can save them. Finally, we’ll be sure to include our navbar partial in each of the views. We’ll only show the movies.ejs
file change here. You can follow the process shown here, or get all the code from our demo GitHub repo.
接下来,我们将更新每个视图以显示表单,而不只是内容。 我们还将添加一个保存按钮,因此如果管理员进行任何更新,他们可以将其保存。 最后,我们将确保在每个视图中都包含我们的导航栏部分。 我们仅在此处显示movies.ejs
文件的更改。 您可以按照此处显示的过程进行操作,也可以从我们的演示GitHub存储库中获取所有代码。
- <!doctype html>
- <html>
- <head>
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
- <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
- <style>
- body {
- background-color: #ecedec;
- }
- </style>
- </head>
- <body>
- <!-- We are including the navbar here -->
- <% include ./includes/navbar %>
-
- <div class="container">
- <h2>Editing: <strong>Movies</strong></h2>
-
- <div class="row clearfix">
- <% movies.forEach(function(movie, index) { %>
- <div class="col-sm-4">
- <div class="panel">
- <div class="panel-heading">
- <h3 class="panel-title">Edit: <%= movie.title %></h3>
- </div>
- <div class="panel-body text-center">
- <label>Name</label>
- <input type="text" class="form-control" value="<%= movie.title %>" />
- <label>Release</label>
- <input type="text" class="form-control" value="<%= movie.release %>" />
- <label>Score</label>
- <input type="text" class="form-control" value="<%= movie.score %>" />
- <label>Reviewer</label>
- <input type="text" class="form-control" value="<%= movie.reviewer %>" />
- <label>Publication</label>
- <input type="text" class="form-control" value="<%= movie.publication %>" />
- </div>
- </div>
- </div>
- <% }) %>
- </div>
- <a class="btn btn-primary btn-block">Save</a>
- </div>
-
- </body>
- </html>
With the server.js
and our views updated, let’s launch the application and navigate to localhost:4000
. If all went well, you should see the homepage showing the new navbar as well as the links to separate pages. Let’s click on the movies link to see how the design is different. We can now also visit the /pending
link by either typing it in the browser bar or clicking on Pending Reviews in the navbar. This time, instead of seeing an error, we’ll get the data successfully returned and will be able to to make edits to it.
在server.js
和视图更新之后,让我们启动应用程序并导航到localhost:4000
。 如果一切顺利,您应该看到显示新导航栏的主页以及指向各个页面的链接。 让我们单击电影链接以查看设计的不同之处。 现在,我们还可以通过在浏览器栏中键入/pending
访问/pending
链接,或单击导航栏中的Pending Reviews 。 这次,我们没有看到错误,而是成功获取了数据并可以对其进行编辑。
Our API works! We have successfully built an API with two disparate clients that have different levels of access to our API. We can add as many additional clients as we want now, and they don’t have to be Node based either. We can create a mobile application or even another web application with say Laravel to consume our API. We’ll just need to create a client and get the Client ID and Secret pair and write some code to exchange this pair of credentials for an access token.
我们的API有效! 我们已经成功地用两个完全不同的客户端构建了一个API,这些客户端对我们的API的访问级别不同。 我们可以根据需要添加任意数量的其他客户端,它们也不必基于Node。 我们可以使用Laravel创建一个移动应用程序甚至另一个Web应用程序来使用我们的API。 我们只需要创建一个客户端,并获取“客户端ID”和“密钥”对,并编写一些代码即可将这对凭据交换为访问令牌。
The services API and clients we built today were fairly simple. Real applications will have many more routes, features, and likely need for granularity. Auth0 luckily supports many of these features out-of-the-box.
我们今天构建的服务API和客户端非常简单。 实际的应用程序将具有更多的路由,功能以及对粒度的需求。 Auth0幸运地支持许多现成的功能。
Other considerations that will be up to you are handling how your backend API interacts with clients. It is recommended that each endpoint be rate-limited so clients don’t overload your API. This is especially important if you have many different clients interacting with your backend. You can use the Auth0 Management API to handle the generation of clients if you don’t like using the Auth0 dashboard. The Management API can handle everything the dashboard can and more which may be useful in the event that you need to revoke access from a particular client, or need to adjust scopes. For more best practices, check out the Auth0 docs concerning API Authentication and Authorization.
处理后端API与客户端的交互方式时需要考虑的其他因素。 建议对每个端点进行速率限制,以使客户端不会使您的API过载。 如果您有许多不同的客户端与后端交互,那么这尤其重要。 如果您不喜欢使用Auth0信息中心,则可以使用Auth0 Management API来处理客户端的生成。 Management API可以处理仪表板可以执行的所有工作,并且在需要撤消来自特定客户端的访问或调整范围的情况下可能有用。 有关更多最佳做法,请查看有关API身份验证和授权的Auth0文档。
Today, we built a backend API that exposed RESTful data to be consumed by independent UI clients. We created two clients, each with a different set of scopes and permissions, to show how we can granularly control access to our API. The Auth0 API Authorization and Authentication feature allowed us to easily protect our API, as well as create clients to interact with our newly secured API. Sign up for a free Auth0 account today and start securing your APIs with ease.
今天,我们构建了一个后端API,该API公开了RESTful数据供独立的UI客户端使用。 我们创建了两个客户端,每个客户端具有一组不同的范围和权限,以显示我们如何精细地控制对API的访问。 Auth0 API授权和身份验证功能使我们能够轻松保护我们的API,并创建客户端以与我们新保护的API进行交互。 立即注册一个免费的Auth0帐户,轻松开始保护您的API。
翻译自: https://scotch.io/tutorials/building-and-securing-a-modern-backend-api
jenkins 构建api
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。