最近项目项目中用angular8进行前端开发,所以近期会收集整理angular相关的文章发出来,中间遇到的坑也会发出来。本文原文是angular6的,不过angular8同样适用.

JWT 是什么,为何要使用 JWT?

JWT 是 JSON Web Tokens 的简称,对于这个问题最精简的回答是,JWT 具有简便、紧凑、安全的特点,具体来看:

简便:只要用户登陆后,使用 JWT 认证仅需要添加一个 http header 认证信息,这可以用一个函数简单实现,我们会在后面的例子中看到这一点。

紧凑:JWT token 是一个 base 64 编码的字符串,包含若干头部信息及一些必要的数据,非常简单。签名后的 JWT 字符串通常不超过 200 字节。

安全:JWT 可以使用 RSA 或 HMAC 加密算法进行加密,确保 token 有效且防止篡改。

总之你可以有一种安全有效的方式来认证用户,并且对所有 api 调用都进行认证,而不需要解析复杂的数据结构或者实现自己的加密算法。

关于 JWT 的详细介绍可以参考 什么是 JWT -- JSON WEB TOKEN

应用概述

交互过程

基于以上背景,我们现在可以来看看如何实现一个真正的应用。例如,假设我们已经通过 node.js 搭建了一个 API 服务器,现在要使用 angular 8 开发一个 todo 待办事项的应用。我们首先来看一下 API 结构:

/auth POST 提交用户名 username 和密码 password 进行登陆认证,返回 JWT 字符串

/todos GET 返回待办事项清单

/todos/{id} GET 返回指定的待办事项

/users GET 返回用户列表

我们将会在后面看到创建这个应用的整个过程,不过首先,我们先关注一下应用的交互过程。我们有一个简单的登陆页面,用户在此输入用户名和密码。当提交登陆表单后,前端应用将数据发送到后台的 /auth 路径。后台服务可以采用合适的方式(数据库查询,调用其他 web service 等)去对这个用户进行认证,最后向前端返回 JWT 字符串。

在本例中, JWT 字符串会包含一些标准声明及私有声明。标准声明是指 JWT 标准中建议使用的 key value 键值对,而私有声明是指仅用于本应用的私有数据:

标准声明

iss: token 的签发者,通常是服务器 FQDN, 但也可以设置成任何客户端应用希望识别的形式。

FQDN:(Fully Qualified Domain Name)全限定域名:同时带有主机名和域名的名称。( 通过符号“.”) 例如:主机名是bigserver,域名是mycompany.com,那么FQDN就是bigserver.mycompany.com。

exp: 过期时间。用 unix 时间戳表示。

nbf: not valid before timestamp。用于标识 token 串启用时间。用 unix 时间戳表示。

私有声明

uid: 登陆用户id。

role: 登陆用户角色。

本例中数据会使用 base64 编码,然后通过 HMAC 算法加密,使用的密钥是 todo-app-super-shared-secret。下面是一个 JWT 字符串的例子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b2RvYXBpIiwibmJmIjoxNDk4MTE3NjQyLCJleHAiOjE0OTgxMjEyNDIsInVpZCI6MSwicm9sZSI6ImFkbWluIn0.ZDz_1vcIlnZz64nSM28yA1s-4c_iw3Z2ZtP-SgcYRPQ

该字符串包含了我们所需要的全部信息,可以保证我们已经合法登陆,且知道登陆的是哪个用户,甚至该用户的角色。

多数应用会将 JWT 存储在 localStorage 或 sessionStorage,但实际如何存储可以自行决定,只要后续在应用中可以方便获取。

当我们访问需要身份认证的 API 服务,最简单的方法是将 JWT 字符串加到 http 头部的 Authorization 字段。

Authorization: Bearer {JWT Token}

当后台服务接收到 JWT, 它可以对其进行解码,使用私钥校验真实性,并通过 exp 和 nbf 值判断其有效性。 iss 字段可以用来确认原始签发者。

当 token 合法性校验完成,服务器即可使用 JWT 中存储的其他信息。例如 uid 可用于识别登陆用户, role 可以用于识别用户角色,判断其是否拥有获取资源的权限。

function getTodos(jwtString)

{

var token = JWTDecode(jwtstring);

if( Date.now() < token.nbf*1000) {

throw new Error('Token not yet valid');

}

if( Date.now() > token.exp*1000) {

throw new Error('Token has expired');

}

if( token.iss != 'todoapi') {

throw new Error('Token not issued here');

}

var userID = token.uid;

var todos = loadUserTodosFromDB(userID);

return JSON.stringify(todos);

}

创建 TODO 应用

为了完成后面的步骤,首先需要安全最新版本的 Node.js (6.x 以上),npm (3.x以上),angular-cli。可以从此处获取到最新版本的 Node.js 及 npm,安装完成后用 npm 安装 angular-cli:

npm install -g @angular/cli

从 github 获取脚手架工程:

git clone https://github.com/sschocke/angular-jwt-todo.git

cd angular-jwt-todo

git checkout pre-jwt

git checkout pre-jwt命令用于将文件切换到实现 JWT 之前的版本。

目录中包含 server 和 client 两个文件夹。server 内存在一个 node api 服务程序,用于提供基本的 api 服务。client 中即为我们解下来要编写的 angular 应用。

Node Api Server

首先启动 API 服务:

cd server

npm install

node app.js

以下链接可以获取相应的 JSON 数据。在实现认证前,我们写死了 todos 接口用于返回 userID=1的任务:

Angular 应用

安装依赖然后启动 client 端服务。

cd client

npm install

npm start

请使用 npm start 而不是 ng serve,因为 npm start 会根据配置文件加上运行参数,将 http 请求转发到 4000 端口

如果一切正常,现在访问 http://localhost:4200 应该可以出现一下界面:

添加 JWT 认证

我们可以安装标准库使 JWT 认证更加简便。

首先在 client 端安装组件。该组件由 Auth0

开发和维护。

cd client

npm install @auth0/angular-jwt

在 server 端安装 body-parse,jsonwebtoken,express-jwt,用于读取 JSON 和 JWT。

cd server

npm install body-parser jsonwebtoken express-jwt

认证 API 接口

在向服务器发送 token 前我们首先要需要一个验证用户的方法。作为简单示例,此处可以先写死用户名和密码。这里最重要的事情是在最后返回 JWT 字符串。

打开 server/app.js,在现有的 require 后面添加下列代码:

const bodyParser = require('body-parser');

const jwt = require('jsonwebtoken');

const expressJwt = require('express-jwt');

app.use(bodyParser.json());

app.post('/api/auth', function(req, res) {

const body = req.body;

const user = USERS.find(user => user.username == body.username);

if(!user || body.password != 'todo') return res.sendStatus(401);

var token = jwt.sign({userID: user.id}, 'todo-app-super-shared-secret', {expiresIn: '2h'});

res.send({token});

});

我们从 /auth 接口获取到传入的 JSON 数据,找到用户名对应的用户,校验密码,如果出现错误,返回 401 Unauthorized HTTP 错误状态。

最重要的部分是 token 的生成。jwt.sign(payload, secretOrPrivateKey, [options, callback]) 方法可以接受以下参数:

payload 是一个键值对象,存储必要的数据,此例中仅包含 user.id,通过该字段,当服务器再次接收到 token 后,就可以解码获得用户 id 并返回相应的资源。

secretOrPrivateKey 此例中为了简化过程,传入的是 HMAC 加密算法私钥。除此以外也可以传入 RSA/ECDSA 私钥。

options 可以传入其他选项,例如这里的 expiresIn,会被转为 exp 标准声明。

callback 用于传入编码完成后的回调函数。

点击此处查看详细用法。

Angular 6 JWT 集成

向 client/src/app/app.modules.ts 添加下列代码,引入 angular-jwt 模块:

import { JwtModule } from '@auth0/angular-jwt';

// ...

export function tokenGetter() {

return localStorage.getItem('access_token');

}

@NgModule({

// ...

imports: [

BrowserModule,

AppRoutingModule,

HttpClientModule,

FormsModule,

// Add this import here

JwtModule.forRoot({

config: {

tokenGetter: tokenGetter,

whitelistedDomains: ['localhost:4000'],

blacklistedRoutes: ['localhost:4000/api/auth']

}

})

],

// ...

}

这些就是必须的基础代码。当然,我们需要更多代码来完成认证过程,不过 angular-jwt 模块主要用来将 JWT 认证信息添加到每个 HTTP请求当中。

tokenGetter() 函数顾名思义,用来获取 token,不过其实现方式由开发者来决定。我们在此处选择从 localStorage 获得 token,将来我们也会将 token 存储在此处。

whiteListedDomains 限制 JWT 发送的域名,这样公开 API 将不会接收到 JWT。

blackListedRoutes 允许我们指定不用接收 JWT 的路径,即使这些路径包含在 whitelisted 域名中。通常我们需要将登陆接口路径加在此处。

共同工作

至此,我们已经有了一个生成 JWT 的接口,并且配置完成往所有 HTTP 请求中加入 JWT。但对于用户来说,还看不到任何变化,我们依然可以进入所有的页面并调用原有接口。

接下来我们需要升级应用,让它判断用户是否登陆,并且升级 API,使其在提供服务前校验 JWT。

下面我们新建一个用于登陆的 angular 组件,一个处理认证请求的服务,以及 Angular Guard 来保护需要登陆的路径。输入下列命令:

cd client

ng g component login --spec=false --inline-style

ng g service auth --flat --spec=false

ng g guard auth --flat --spec=false

现在 client 目录中已经添加了下列文件:

src/app/login/login.component.html

src/app/login/login.component.ts

src/app/auth.service.ts

src/app/auth.guard.ts

接着我们将 service 和 guard 添加到应用引用中。更新 client/src/app/app.modules.ts:

import { AuthService } from './auth.service';

import { AuthGuard } from './auth.guard';

// ...

providers: [

TodoService,

UserService,

AuthService,

AuthGuard

],

然后更新client/src/app/app-routing.modules.ts文件,将路径保护起来,并且为登陆组件添加一个路由。

// ...

import { LoginComponent } from './login/login.component';

import { AuthGuard } from './auth.guard';

const routes: Routes = [

{ path: 'todos', component: TodoListComponent, canActivate: [AuthGuard] },

{ path: 'users', component: UserListComponent, canActivate: [AuthGuard] },

{ path: 'login', component: LoginComponent},

// ...

最后,更新client/src/app/auth.guard.ts:

import { Injectable } from '@angular/core';

import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private router: Router) { }

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

if (localStorage.getItem('access_token')) {

return true;

}

this.router.navigate(['login']);

return false;

}

}

在这个示例应用中,我们只是简单检查 JWT 是否存储在本地存储中。在实际应用中,我们还需要解码 token 来校验合法性、有效时间等。JwtHelperService 可以帮助我们完成这些工作。

此时,我们的应用将只会把页面定向到登陆页面,因为现在还没有完成登陆的办法。下面编写client/src/app/auth.service.ts:

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';

import { map } from 'rxjs/operators';

@Injectable()

export class AuthService {

constructor(private http: HttpClient) { }

login(username: string, password: string): Observable {

return this.http.post('/api/auth', {username: username, password: password})

.pipe(

map(result => {

localStorage.setItem('access_token', result.token);

return true;

})

);

}logout(){

localStorage.removeItem('access_token');

}public get loggedIn(): boolean{

return (localStorage.getItem('access_token') !== null);

}}

认证服务只有两个方法,login 和 logout:

login 将 username 和 password 发送到后台,等接收到返回的 JWT 后将其存储到 localStorage,键值为 access_token,为了简化,此处没有进行错误处理。

logout 简单从 localStorage 清除了·access_token` 的值。

loggedIn 返回一个布尔值,我们可以用来判断用户是否登陆。

最后修改登陆组件,编辑client/src/app/login/login.components.html:

{{error}}

Username

Password

Login

client/src/app/login/login.components.ts:

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../auth.service';

import { Router } from '@angular/router';

import { first } from 'rxjs/operators';

@Component({

selector: 'app-login',

templateUrl: './login.component.html'

})

export class LoginComponent {

public username: string;

public password: string;

public error: string;

constructor(private auth: AuthService, private router: Router) { }

public submit() {

this.auth.login(this.username, this.password)

.pipe(first())

.subscribe(

result => this.router.navigate(['todos']),

err => this.error = 'Could not authenticate'

);

}

}

此处需要重新运行服务端 app.js

现在我们的应用将会变成这样:

此时我们可以登陆,查看所有的界面(用户名jemma,paul,sebastian,密码todo)。但我们的应用只能显示相同的导航,且不具有登出的功能。让我们在改进 api 前来修正这些问题。

将 client/src/app/app.component.ts 文件的内容替换如下:

import { Component } from '@angular/core';

import { Router } from '@angular/router';

import { AuthService } from './auth.service';

@Component({

selector: 'app-root',

templateUrl: './app.component.html',

styleUrls: ['./app.component.css']

})

export class AppComponent {

constructor(private auth: AuthService, private router: Router) { }

logout() {

this.auth.logout();

this.router.navigate(['login']);

}

}

打开 client/src/app/app.component.html ,将  标签中的内容替换如下:

Todo ListUsersLoginLogout

如此我们已经让导航栏与内容相关,并且根据登陆状态选择菜单是否隐藏。

API 安全性

现在的问题是,对于三个不同的用户,后台返回的 TODO 列表是一样的。这是因为现在 /todos 接口对所有用户返回的是相同的 userID=1 的待办事项。我们在代码中并没有去获取登陆用户。

我们可以在 server/app.js 文件中新增 app.use():

app.use(expressJwt({secret: 'todo-app-super-shared-secret'}).unless({path: ['/api/auth']}));

利用 express-jwt 中间件,获取到 JWT 中包含的数据,在接口处理函数中可以用 req.user.userID的形式获取。下面改写 /todos 接口方法:

res.send(getTodos(req.user.userID));

重启服务后即可根据用户返回列表内容。

php 完全前后端分离使用jwt,前后端分离,在 angular 8 中利用 JWT 进行身份认证相关推荐

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

    版权声明:本文为博主chszs的原创文章,未经博主允许不得转载. https://blog.csdn.net/chszs/article/details/79639919 在SPA应用中利用JWT进行 ...

  2. asp net html.dropdownlist viewdata 指定选中项_ASP.NET Web API基础(05)--- 基于JWT的身份认证 - 高原秃鹫...

    5.1 Web API中的过滤器 WebApi下的过滤器和MVC下的过滤器有一些区别. (1)       所处命名空间不同. Web API 过滤器额命名空间是"",而MVC过滤 ...

  3. 基于JWT的身份认证学习笔记

    JSON Web Token(缩写JWT)是目前最流行的跨域认证解决方案. 一.跨域认证的问题 互联网服务离不开用户认证.一般流程是下面这样. 1.用户向服务器发送用户名和密码. 2.服务器验证通过后 ...

  4. 前后端分离中使用基于jwt的token进行身份认证

    基于jwt的Token认证机制可以看之前的文章: 基于JWT的Token认证机制实现 在前后端分离中,我们与前端约定一种身份认证机制.当用户登录的时候,我们会返回给前端一个token,前端会将toke ...

  5. 利用JWT安全验证(前后端分离,单点登录,分布式微服务)

    JWT官网: https://jwt.io/ JWT(Java版)的github地址:https://github.com/jwtk/jjwt JWT请求流程 用户使用账号和面发出post请求: 服务 ...

  6. 【Gorho】springboot整合Shiro+jwt 前后端分离 超级详细的shiro+jwt鉴权过程

    shiro+jwt+springboot 说在前面 简介 项目环境(pom.xml) 项目结构(各种包和类) 鉴权流程 具体代码 配置Shiro 配置JWTUtils 定义JwtFilter 定义Jw ...

  7. springboot jwt token前后端分离_实战:十分钟实现基于JWT前后端分离的权限框架

    前言 面试过很多Java开发,能把权限这块说的清楚的实在是不多,很多人因为公司项目职责问题,很难学到这类相关的流程和技术,本文梳理一个简单的场景,实现一个基于jwt前后端分离的权限框架. 简易流程 登 ...

  8. boke | 前后端分离中使用JWT保持前端数据的持久化,并自动登录

    在boke后台登录系统中实现了自动登录的功能,这也是前后端分离的开发模式中最常见的一个问题,如何保持登录状态的持久化.今天就来通过实现自动登录来一步步理清前端数据持久化的思路. 一 实现思路 当用户首 ...

  9. node.js学习总结:node.js的内置模块,模块化,npm与包 express,前后端身份认证 JWT认证机制

    node.js学习总结 什么是node.js node.js的内置模块 fs系统模块 path路径模块 http模块 模块化 npm与包 express express路由 express+mysql ...

最新文章

  1. 怎样设计一个商城项目?
  2. requests模块--python发送http请求
  3. vb.net2019-多线程并行计算(1)
  4. OpenSitUp开源项目:零基础开发基于姿态估计的运动健身APP
  5. 多个数字数组_七个问题帮助初学者深入理解Java数组
  6. MySQL数据库的回滚失败(JAVA)
  7. python3 random模块_Python3 中 random模块
  8. 国内四家物联网实时操作系统浅析
  9. We FALL ASleep At Night, We Do REST Right
  10. 5.RabbitMQ实战 --- 集群并处理失败
  11. NI MultiSim 正版软件的下载链接
  12. 博微写狗.exe和博微电力工程造价深思4写狗
  13. PowerDesigner如何自定义报表模板
  14. Android启动优化--异步优化
  15. ALPS新秀登场TRON DeFi——让每个人都可参与的社会金融
  16. C++多线程匿名聊天室(控制台)
  17. oracle ins ctx.mk,安装Oracle10g遭遇ins_ctx.mk问题解决方法
  18. MaskNet 这个CTR模型,有点意思
  19. docker镜像指定安装源_如何修改docker pull镜像源
  20. 电大计算机理工英语19年秋季,国家开放大学2020年秋季学期电大《理工英语1》形成性考核答案...

热门文章

  1. NVDIMM 知识点
  2. android中检测网速,Android 流量与网速监测(悬浮窗) 源码
  3. vue-cli搭建项目,使用localhost或ip地址均可访问
  4. 经典神经网络论文超详细解读(八)——ResNeXt学习笔记(翻译+精读+代码复现)
  5. SmartRefreshLayout 下拉刷新上拉加载框架
  6. eve-ng ubuntu 20.04 设置iptables
  7. 第十三届蓝桥杯 2022年省赛真题(Java 大学C组)
  8. iPhone 14 Pro Max 和 iPhone 14 Pro的区别
  9. KOL新消费品牌营销知多少
  10. java中输出第一个a的的位置,java小编程--在一个A字符串中找到与B字符串一样的,返回B字符串出现的第一个位置...