认证Express中间件(SSR)¶
Feathers身份验证还支持验证Express中间件的路由,并可用于服务器端呈现.此配方显示如何创建登录表单, /logout 端点和受保护的 /chat 端点,该端点呈现来自我们的所有用户和最近的聊天消息 chat/readme.
关键步骤是:
通过oAuth或本地身份验证流程获取JWT
在cookie中设置JWT(因为浏览器会在每次请求时发送它)
在需要保护的任何中间件之前,添加
cookieParser()和authenticate('jwt')authentication Express中间件.这将从JWT中的用户信息设置req.user, 或者如果没有JWT则显示401错误页面或它是无效的.
配置¶
为了使浏览器在每次请求时都发送JWT,必须在身份验证配置中启用cookie.
注解
如果您使用的是oAuth2,则已启用Cookie.
如果尚未启用,请将以下内容添加到 config/default.json 中的 authentication 部分:
"cookie": {
"enabled": true,
"name": "feathers-jwt"
}
我们希望通过向 /authentication 端点提交普通的HTML表单来使用用户名和密码登录进行身份验证.默认情况下,对该端点的成功POST将使用我们的JWT呈现JSON.这适用于REST API,但在我们的例子中,我们希望被重定向到受保护的页面.我们可以通过在 config/default.json 中 authentication 配置的 local 策略部分设置 successRedirect 来做到这一点:
"local": {
"entity": "user",
"usernameField": "email",
"passwordField": "password",
"successRedirect": "/chat"
}
设置中间件¶
../api/authentication/jwt 将在cookie中查找JWT,但只有解析cookie的路径才能访问它.这可以通过 cookie-parser Express中间件 来完成:
npm install cookie-parser
现在我们可以通过首先向链中添加 cookieParser(),authenticate('jwt') 来保护任何Express路由.
注解
只有在实际需要由cookie中的JWT保护的路由之前注册cookie解析器中间件才能防止CSRF安全问题.
由于我们想要在服务器上呈现视图,我们必须注册一个 Express模板引擎. 在本例中,我们将使用 EJS:
npm install ejs
接下来,我们可以将 src/middleware/index.js 更新为
将视图引擎设置为EJS(Express中视图的默认文件夹是项目根目录中的
views/)注册一个
/login路由,呈现views/login.ejs注册一个protected ../api/application,然后渲染
views/chat.ejs注册一个
/logout路由,删除cookie并重定向回登录页面
注解
我们也可以使用 feathers generate middleware 生成中间件,但由于它们都很短,我们现在可以将它保存在同一个文件中.
const cookieParser = require('cookie-parser');
const { authenticate } = require('@feathersjs/authentication').express;
module.exports = function (app) {
// Use EJS as the view engine (using the `views` folder by default)
app.set('view engine', 'ejs');
// Render the /login view
app.get('/login', (req, res) => res.render('login'));
// Render the protected chat page
app.get('/chat', cookieParser(), authenticate('jwt'), async (req, res) => {
// `req.user` is set by `authenticate('jwt')`
const { user } = req;
// Since we are rendering on the server we have to pass the authenticated user
// from `req.user` as `params.user` to our services
const params = {
user, query: {}
};
// Find the list of users
const users = await app.service('users').find(params);
// Find the most recent messages
const messages = await app.service('messages').find(params);
res.render('chat', { user, users, messages });
});
// For the logout route we remove the JWT from the cookie
// and redirect back to the login page
app.get('/logout', cookieParser(), (req, res) => {
res.clearCookie('feathers-jwt');
res.redirect('/login');
});
};
注解
npm ls @feathersjs/authentication-jwt 必须显示已安装2.0.0或更高版本.
查看¶
登录表单必须向 /authentication 端点发出POST请求,并发送与任何其他API客户端相同的字段.在我们的案例中具体:
{
"strategy": "local",
"email": "user@example.com",
"password": "mypassword"
}
email 和 passwords 是正常的输入字段,我们可以将 strategy 添加为隐藏字段.表单必须向 /authentication 端点提交POST请求.由于服务可以接受JSON和URL编码形式,因此我们不需要做任何其他事情. views/login.ejs 的登录页面如下所示:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
<title>Feathers chat login</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/base.css">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/chat.css">
</head>
<body>
<div id="app" class="flex flex-column">
<main class="login container">
<div class="row">
<div class="col-12 col-6-tablet push-3-tablet text-center heading">
<h1 class="font-100">Log in</h1>
</div>
</div>
<div class="row">
<div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
<form class="form" method="post" action="/authentication">
<input type="hidden" name="strategy" value="local">
<fieldset>
<input class="block" type="email" name="email" placeholder="email">
</fieldset>
<fieldset>
<input class="block" type="password" name="password" placeholder="password">
</fieldset>
<button type="submit" id="login" class="button button-primary block signup">
Log in
</button>
</form>
</div>
</div>
</main>
</div>
</body>
</html>
views/chat.ejs 页面有 users, user (经过身份验证的用户)和 messages 属性,我们在 /chat 中间件中传递了它们.渲染消息和用户看起来类似于 chat/frontend:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
<title>Feathers chat</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/base.css">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/chat.css">
</head>
<body>
<div id="app" class="flex flex-column">
<main class="flex flex-column">
<header class="title-bar flex flex-row flex-center">
<div class="title-wrapper block center-element">
<img class="logo" src="http://feathersjs.com/img/feathers-logo-wide.png"
alt="Feathers Logo">
<span class="title">Chat</span>
</div>
</header>
<div class="flex flex-row flex-1 clear">
<aside class="sidebar col col-3 flex flex-column flex-space-between">
<header class="flex flex-row flex-center">
<h4 class="font-300 text-center">
<span class="font-600 online-count">
<%= users.total %>
</span> users
</h4>
</header>
<ul class="flex flex-column flex-1 list-unstyled user-list">
<% users.data.forEach(user => { %><li>
<a class="block relative" href="#">
<img src="<%= user.avatar %>" alt="" class="avatar">
<span class="absolute username"><%= user.email %></span>
</a>
</li><% }); %>
</ul>
<footer class="flex flex-row flex-center">
<a href="/logout" id="logout" class="button button-primary">
Sign Out
</a>
</footer>
</aside>
<div class="flex flex-column col col-9">
<main class="chat flex flex-column flex-1 clear">
<% messages.data.forEach(message => { %>
<div class="message flex flex-row">
<img src="<%= message.user && message.user.avatar %>"
alt="<%= message.user && message.user.email %>" class="avatar">
<div class="message-wrapper">
<p class="message-header">
<span class="username font-600">
<%= message.user && message.user.email %>
</span>
<span class="sent-date font-300"><%= new Date(message.createdAt).toString() %></span>
</p>
<p class="message-content font-300"><%= message.text %></p>
</div>
</div>
<% }); %>
</main>
</div>
</div>
</main>
</div>
</body>
</html>
如果我们现在启动服务器(npm start)并转到 localhost:3030/login 我们可以看到登录页面.我们可以使用在 chat/frontend 中创建的用户之一的登录信息,一旦成功,我们将被重定向到 /chat, 显示所有当前消息和用户的列表,然后单击 Sign out 按钮会将我们注销并重定向到登录页面.