在SPA应用中利用JWT进行身份验证


在SPA应用中利用JWT进行身份验证

  • 2018.3.21
  • 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。

SPA

SPA即Single Page Application,单页应用程序,是一种Web应用程序或网站,通过动态地重写当前的页面,而不是从服务器端加载一个新网页与用户交互。此方法避免了连续页面之间的用户体验的中断,使应用程序更像是桌面应用程序。在SPA中,所有必需的代码(HTML,JavaScript和CSS)都是通过单页加载来检索的,或者适当的资源是动态加载的并根据需要添加到页面中,通常是响应用户操作。尽管可以使用位置散列或HTML5 History API来提供应用程序中单独的逻辑页面的感知和导航性,但页面在该过程的任何时间点都不会重新加载,也不会将控件转移到另一个页面。与单页面应用程序的交互通常涉及在后台与Web服务器的动态通信。像Angular.js、Ember.js、Meteor.js、ExtJS、React等JS框架都采用了SPA原则。

JWT

JWT即JSON Web Token,是一个基于JSON的开放标准(RFC-7519),它用于创建访问令牌以判断权利要求。例如,服务器可能会生成一个令牌,其中声明“以管理员身份登录”并将其提供给客户端。然后客户端可以使用该令牌来证明它以管理员身份登录。令牌由服务器密钥签名,因此客户端和服务器都能够验证令牌是否合法。JWT令牌被设计成紧凑的、URL-safe的、尤其适合在Web浏览器单点登录SSO的上下文。JWT声明通常可用于传递身份提供者与服务提供者之间的身份验证用户身份,或业务流程所要求的任何其他类型的声明。JWT令牌还可以被认证和加密。JWT标准还依赖于其它基于JSON的标准,比如RFC-7515的JWS(JSON Web Signature)、RFC-7516的JWE(JSON Web Encryption)。

一个成功的令牌认证系统要求用户了解安全细节和其他认证凭证。SPA应用通常与Restful API紧密联系在一起,将UI与所需数据进行绑定,并为UI带来更好的外观和感觉。但问题是,怎样以安全的方式实现API访问?

一个这样的SPA框架是Angular。我们可以借助基于JSON Web Token的身份验证来验证我们的Angular应用程序。因此,当创建Angular应用程序时,一个大问题是,您如何知道您的用户是谁以及他们可以访问哪些内容?

针对所有这些问题的答案以及针对API的认证和授权问题的解决方案是JWT认证系统,它正在取代传统的基于cookie的认证。这给你一个很好的方式来声明一个用户及其在应用程序中的访问权限。它为您提供了一个加密报头作为令牌,以保护经身份验证的用户对应用程序的访问,并为您的前端和后端构建访问控制逻辑。

让我们通过在您的Angular应用程序(版本2+)中查看几个验证代码来深入了解它。

前端工作

当用户发出登录请求时,我们通过我们的身份验证服务调用后端,如下所示:

login(username: string, password: string): Observable < boolean > {
    return this.http.post('/api/authenticate', JSON.stringify({
            username: username,
            password: password
        }))
        .map((response: Response) => {
            // login successful if there's a jwt token in the response
            let token = response.json() && response.json().token;
            if (token) {
                // set token property
                this.token = token;
                // store username and jwt token in local storage to keep user logged in between page refreshes
                localStorage.setItem('currentUser', JSON.stringify({
                    username: username,
                    token: token
                }));
                // return true to indicate successful login
                return true;
            } else {
                // return false to indicate failed login
                return false;
            }
        });
}

服务器端工作

当用户尝试使用用户名和密码登录应用程序时,服务器端将:

  • 验证用户
  • 生成令牌
  • 将令牌发送给用户

后端可以是任何像Node.js这样的服务器端框架,Node.js使用npm的jsonwebtoken模块来签署和验证JSON Web令牌。以下是一些示例代码:

模块

var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");

登录功能

login: function (req, res) {
    // this is param checking if they are provided 
    if (!_.has(req.body, 'email') || !_.has(req.body, 'password')) {
        return res.serverError("No field should be empty.");
    }
    // check if the username matches any email or phoneNumber
    User.findOne({
        email: req.body.email
    }).exec(function callback(err, user) {
        if (err) return res.serverError(err);
        if (!user) return res.serverError("User not found, please sign up.");
        //check password
        bcrypt.compare(req.body.password, user.password, function (error, matched) {
            if (error) return res.serverError(error);
            if (!matched) return res.serverError("Invalid password.");
            //save the date the token was generated for already inside toJSON()
            var token = jwt.sign(user.toJSON(), "this is my secret key", {
                expiresIn: '10m'
            });
            //return the token here
            res.ok(token);
        });
    });
}

令牌返回如何工作

用户使用其凭据成功登录后,会返回一个Web令牌,它以本地存储方式进行保存。现在,无论何时用户想要访问受保护的路由,都应该在请求报头中携带授权报头项发送JWT。报头的内容如下所示:

Authorization: Bearer 

因此,JWT将被用于认证和授权API,这些API将授予访问其受保护的路由和资源的权限。

使用Angular处理Web令牌

由于JWT现在保存在本地存储中,我们可以使用它来防止未经身份验证的用户访问受限制的路由。它用于路由模块到任何受保护的路由。为此,我们使用Angular 2中的AuthGuard。AuthGuard使用了来自@angular/router的CanActivate方法。

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private router: Router) { }
    canActivate() {
        if (localStorage.getItem('currentUser')) {
        // logged in so return true
            return true;
        }
        // not logged in so redirect to login page
        this.router.navigate(['/login']);
        return false;
    }
}

因此,可以像这样在路由模块中保护路由:

{ path: '', component: SomeComponent, canActivate: [AuthGuard] }

接下来,我们使用此令牌来获取SPA应用所需的数据。它可以访问用户的API端点。此端点将以SPA为用户需要的数据作出响应。我们可以使用用户服务,如下所示。

getUsers(): Observable<User[]> {
    // add authorization header with jwt token
    let headers = new Headers({ 'Authorization': 'Bearer ' + this.authenticationService.token });
    let options = new RequestOptions({ headers: headers });
    // get users from api
    return this.http.get('/api/users', options)
            .map((response: Response) => response.json());
}

注意我们通过使用RequestOptions实现了在报头Header中携带了上面讨论的Authorization Bearer项。为此,可以使用npm中的另一个模块,即’angular2-jwt’。它是帮助构建Angular 2应用程序的辅助工具库。当从应用程序发出HTTP请求时,它会自动将JWT作为授权报头附加。它使用AuthHttp类发送一个JWT请求,从而有条件地允许基于JWT状态的路由导航。它可以按照如下所示进行安装。

npm install angular2-jwt 

AuthHTTP可以在调用用户API时使用,如下所示:

// get users from api
return this.authHttp.get('/api/users')
        .map((response: Response) => response.json());

最后

本文的主旨是提供一个关于JWT的概述,以及如何使用JWT来保护单页应用程序。


发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>