在Angular中使用JWT认证方法示例


Posted in Javascript onSeptember 10, 2018

本文介绍了在Angular中使用JWT认证方法示例,分享给大家,具体如下:

在Angular中使用JWT认证方法示例

项目地址: grading-system

基于session的认证和基于token的认证的方式已经被广泛使用。在session认证中,服务端会存储一份用户登录信息,这份登录信息会在响应时传递给浏览器并保存为Cookie,在下次请求时,会带上这份登录信息,这样就能识别请求来自哪个用户。

在基于session的认证中,每个用户都要生成一份session,这份session通常保存在内存中,随着用户量的增加,服务端的开销会增大,而且对分布式应用不是很友好。

在token认证中,服务端不需要保留用户认证信息。当用户登录时,服务器验证用户信息后会返回一个token,这个token存储在客户端,并且在每次请求的请求头中都带上这个token,这样服务端验证token后就可以返回数据。

JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。 此信息可以通过数字签名进行验证和信任。特别适用于分布式站点的单点登录(SSO)场景。

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

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

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

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

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

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

JWT的构成

JWT由 . 分隔的三个部分组成,它们是:

  • 头部(Header)
  • 荷载(Playload)
  • 签名(Signature)

也就是说,JWT只是一个具有以下格式的字符串:

header.payload.signature

头部

头部通常由两部分组成:令牌的类型(即JWT)以及正在使用的散列算法,例如HMAC SHA256或RSA。

header.payload.signature

然后,对这个JSON进行Base64编码,形成JWT的第一部分。

ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9

荷载

JWT的第二部分是荷载,其中包含声明。 声明是关于实体(通常是用户)和其他数据的声明。声明有三种:注册的声明、公开的声明和私有的声明。

JWT规范定义了七个在标准中注册的声明名称,它们是:

  • iss: JWT签发者
  • sub: JWT所面向的用户
  • aud:接收JWT的一方
  • exp:JWT的过期时间,这个过期时间必须要大于签发时间
  • nbf:定义在什么时间之前,该JWT都是不可用的.
  • iat: JWT的签发时间
  • jti: JWT的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

对于特定情况,可以使用公共的声明名称。 这些包括:

  • auth_time:身份验证发生的时间
  • acr:认证上下文类的引用
  • nonce:用于将客户端会话与ID Token关联的值

最后,还有私有的声明名称,可以使用它们来传达与身份相关的信息,例如姓名或部门。

由于公共和私人的声明未注册,请注意避免名称冲突。

比如,我们定义一个palyload:

{
 "sub": "1234567890",
 "name": "tc9011",
 "admin": true,
 "exp": 1441594722
}

然后将其进行base64加密,得到JWT的第二部分:

ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAidGM5MDExIiwKICAiYWRtaW4iOiB0cnVlLAogICJleHAiOiAxNDQxNTk0NzIyCn0=

签名

签名由base64编码后的头、base64编码后的荷载和secret组成。

例如,将上面的两个编码后的字符串都用句号 . 连接在一起(头部在前),就形成了:

ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAidGM5MDExIiwKICAiYWRtaW4iOiB0cnVlLAogICJleHAiOiAxNDQxNTk0NzIyCn0=

然后,将上面拼接完的字符串用secret作为秘钥进行HS256加密。

HMACSHA256(
 base64UrlEncode(header) + "." +
 base64UrlEncode(payload),
 secret)

使用JWT

一般在会在请求头中加入 Authorization ,并加上 Bearer 进行标注:

在Angular中使用JWT认证方法示例

fetch('api/v1/user/1', {
 headers: {
  'Authorization': 'Bearer ' + token
 }
})

服务端会验证token,如果验证通过就会返回相应的资源。

不过要注意,因为荷载是base64编码,这种编码可以对称解密,所以在荷载中不应该存放用户的敏感信息,比如密码。所以一般JWT用来向Web传递一些非敏感信息,例如用户名、所属部门等。

在Angular中使用JWT

这里我们以Angular6和koa2(使用TypeScript)为例,介绍一下如何在你的Angular应用中使用JWT。

服务端

首先在jwt.io 官网上找到node的JWT的库: jsonwebtoken 。

在Angular中使用JWT认证方法示例

可以看到官网把这个库对标准注册声明字段的支持情况以及加密方式的支持情况都列出来了。除了这个库,还需要使用koa一个中间件: koa-jwt ,用来对HTTP请求进行JWT认证。你可以通过下面命令安装这两个库:

npm i koa-jwt jsonwebtoken --save

app.ts 中:

import * as jwt from 'koa-jwt';

app.use(jwt({
   secret: Secret
  }).unless({
   path: [/\/register/, /\/login/, /\/groups/],
  }));

这里的secret就是你自己定义的秘钥, unless 方法用来排除一些不需要进行JWT认证的api。koa-jwt中间件需要放在路由中间件之前,这样就可以对所有路由(除了 unless 中设置的路由外)进行JWT的检查。只有正确之后才能正确的访问。

除此之外,你还要自定义一个401错误处理的中间件,如果没有token,或者token失效,该中间件会给出对应的错误信息。如果没有自定义中间件的话,会直接将 koa-jwt 暴露的错误信息直接返回给用户。

export const errorHandle = (ctx, next) => {
 return next().catch((err) => {
  if (err.status === 401) {
   ctx.status = 401;
   handleError({ctx, message: '登录过期,请重新登录', err: err.originalError ? err.originalError.message : err.message});
  } else {
   throw err;
  }
 });
};

然后把这个中间件放在koa-jwt之前:

app.use(errorHandle);

app.use(jwt({
  secret: Secret
}).unless({
  path: [/\/register/, /\/login/, /\/groups/],
}));

在用户登陆时候,生成token,返回给客户端:

// 生成 token 返回给客户端
const token = jsonwebtoken.sign({
  user: {
    workNumber: user.workNumber,
    realName: user.realName,
    group: user.group,
    role: user.role
  },

  // 设置 token 过期时间
  exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24),  // 1天
}, Secret);

handleSuccess({
  ctx,
  message: '登陆成功!',
  response: {
    token,
    lifeTime: Math.floor(Date.now() / 1000) + (60 * 60 * 24)  // 1天
  }
});

需要注意的是,在使用 jsonwebtoken.sign() 时,需要传入的 secret 参数,这里的 secret 必须要与 前面设置 jwt() 中的 secret 一致。

客户端

在Angular中,我们需要使用 @auth0/angular2-jwt 这个库来帮助我们在Angular中处理JWT:

npm install @auth0/angular-jwt --save

app.module.ts 中引入 JwtModule 这个模块(注意,引入该模块的同时也要引入 HttpClientModule 模块):

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

export function tokenGetter(){
 return localStorage.getItem('token');
}

@NgModule({
 bootstrap: [AppComponent],
 imports: [
  // ...
  HttpClientModule,
  JwtModule.forRoot({
   config: {
    tokenGetter: tokenGetter,
    whitelistedDomains: ['localhost:3001'],
    blacklistedRoutes: ['localhost:3001/auth/']
   }
  })
 ]
})
export class AppModule {}

JwtModuleconfig 中:

tokenGetter :从localStorage中获取token;

whitelistedDomains :允许发送认证的请求的域名;

blacklistedRoutes :你不希望替换header中 Authorization 信息的api列表。

接着创建一个全局的 auth.service.ts 服务,方便在登陆的时候获取用户相关信息及权限,这个服务中有个 login 方法,用来处理登陆后返回的token信息,并把token存到LocalStorage中,这样在token失效前,下次用户登陆时就不需要输入用户名和密码:

login(loginInfo: LoginInfo): Observable<boolean> {
  return this.passportService.postLogin(loginInfo).pipe(map(
    (res: LoginRes) => {
      // 登陆成功后获取token,并存到localStorage
      this.storageService.setLocalStorage('token', res.token);
      const decodedUser = this.decodeUserFromToken(res.token);
      this.setCurrentUser(decodedUser);
      this.msg.success('登录成功!');
      return this.loggedIn;
    }
  )
                             );
}

在这个 login 方法中, decodeUserFromToken 封装了 @auth0/angular2-jwt 中提供的 decodeToken 方法,注意 decodeToken 方法解析出来的只是服务端 jsonwebtoken.sign() 中的JSON对象,所以需要通过 . 操作获取 jsonwebtoken.sign() 中定义的 user

decodeUserFromToken(token): User {
  return this.jwtHelperService.decodeToken(token).user;
}

在这个服务中,定义了两个变量 loggedInisAdmin ,用来标识用户是否登录和其相应的权限,方便在Angular路由中控制可以访问的视图。

有登录当然就有登出,登出时只需把token从LocalStorage中移除,并把几个变量重置即可:

logout(): void {
  this.storageService.removeLocalStorage('token');
  this.loggedIn = false;
  this.isAdmin = false;
  this.currentUser = new User();
}

AuthService 的完整代码如下:

import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';

import { JwtHelperService } from '@auth0/angular-jwt';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { LoginInfo, LoginRes, User } from '../../views/passport/interfaces/passport';
import { PassportService } from '../../views/passport/services/passport.service';
import { StorageService } from '../storage/storage.service';
import { NzMessageService } from 'ng-zorro-antd';


@Injectable()
export class AuthService {
 public loggedIn = false;
 public isAdmin = false;
 public currentUser: User = new User();

 constructor(private jwtHelperService: JwtHelperService,
       private router: Router,
       private injector: Injector,
       private passportService: PassportService,
       private storageService: StorageService) {
  const token = localStorage.getItem('token');
  if (token) {
   const decodedUser = this.decodeUserFromToken(token);
   this.setCurrentUser(decodedUser);
  }
 }

 get msg(): NzMessageService {
  return this.injector.get(NzMessageService);
 }

 login(loginInfo: LoginInfo): Observable<boolean> {
  return this.passportService.postLogin(loginInfo).pipe(map(
   (res: LoginRes) => {
     this.storageService.setLocalStorage('token', res.token);
     const decodedUser = this.decodeUserFromToken(res.token);
     this.setCurrentUser(decodedUser);
     this.msg.success('登录成功!');
     return this.loggedIn;
    }
   )
  );
 }

 logout(): void {
  this.storageService.removeLocalStorage('token');
  this.loggedIn = false;
  this.isAdmin = false;
  this.currentUser = new User();
 }

 decodeUserFromToken(token): User {
  return this.jwtHelperService.decodeToken(token).user;
 }

 setCurrentUser(decodedUser): void {
  this.loggedIn = true;
  this.currentUser.workNumber = decodedUser.workNumber;
  this.currentUser.realName = decodedUser.realName;
  this.currentUser.group = decodedUser.group;
  this.currentUser.role = decodedUser.role;
  this.isAdmin = decodedUser.role > 10;
  delete decodedUser.role;
 }
}

至此,在你的Angular应用中就引入了JWT认证,当然,你也可以不使用 @auth0/angular2-jwt ,自己手写一个HTTP拦截器,手动设置每次请求的header:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>,
       next: HttpHandler): Observable<HttpEvent<any>> {

    const token = localStorage.getItem("token");

    if (token) {
      const cloned = req.clone({
        headers: req.headers.set("Authorization",
          "Bearer " + token)
      });

      return next.handle(cloned);
    }
    else {
      return next.handle(req);
    }
  }
}

不过这样的话,token Base64解码也需要自己手写,稍微麻烦一点。

总结

JWT因为是基于JSON的,所以通用性很强,很多语言已经存在jwt相关的库。不过使用JWT的时候需要注意以下几点:

  • 保存好secret秘钥,这个秘钥只能在服务端存在
  • 给token设置一个过期时间,因为一旦token生成,它就永远有效,除非token密钥被更改或过期
  • 在payload中只能存储一些业务逻辑所必要的非敏感信息

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Ext JS 4实现带week(星期)的日期选择控件(实战二)
Aug 21 Javascript
基于jquery异步传输json数据格式实例代码
Nov 23 Javascript
node.js中的emitter.emit方法使用说明
Dec 10 Javascript
Javascript基础教程之数据类型 (布尔型 Boolean)
Jan 18 Javascript
js实现YouKu的漂亮搜索框效果
Aug 19 Javascript
jQuery的extend方法【三种】
Dec 14 Javascript
详解JavaScript常量定义
Jan 03 Javascript
详解vuex结合localstorage动态监听storage的变化
May 03 Javascript
浅析node.js的模块加载机制
May 25 Javascript
VSCode使用之Vue工程配置eslint
Apr 30 Javascript
JavaScript静态作用域和动态作用域实例详解
Jun 17 Javascript
javascript设计模式 ? 模板方法模式原理与用法实例分析
Apr 23 Javascript
详解vue-router传参的两种方式
Sep 10 #Javascript
详解Vue.js使用Swiper.js在iOS
Sep 10 #Javascript
webuploader分片上传的实现代码(前后端分离)
Sep 10 #Javascript
在Vue 中使用Typescript的示例代码
Sep 10 #Javascript
ES6使用export和import实现模块化的方法
Sep 10 #Javascript
Vuejs 实现简易 todoList 功能 与 组件实例代码
Sep 10 #Javascript
浅谈微信小程序flex布局基础
Sep 10 #Javascript
You might like
如何获得PHP相关资料
2006/10/09 PHP
php.ini中文版
2006/10/09 PHP
解析PHP工厂模式的好处
2013/06/18 PHP
PHP上传文件参考配置大文件上传
2015/12/16 PHP
PHP中in_array函数使用的问题与解决办法
2016/09/11 PHP
php通过pecl方式安装扩展的实例讲解
2018/02/02 PHP
PHP使用OB缓存实现静态化功能示例
2019/03/23 PHP
PHP7 安装event扩展的实现方法
2019/10/08 PHP
laravel实现上传图片的两种方式小结
2019/10/12 PHP
javascript 客户端验证上传图片的大小(兼容IE和火狐)
2009/08/15 Javascript
JavaScript DOM 学习第七章 表单的扩展
2010/02/19 Javascript
JQuery,Extjs,YUI,Prototype,Dojo 等JS框架的区别和应用场景简述
2010/04/15 Javascript
jquery load()在firefox(火狐)下显示不正常的解决方法
2011/04/05 Javascript
高效率JavaScript编写技巧整理
2013/08/23 Javascript
javascript生成json数据简单示例分享
2014/02/14 Javascript
jquery动态更换设置背景图的方法
2014/03/25 Javascript
使用javascript获取页面名称
2014/12/23 Javascript
轻松实现jquery手风琴效果
2016/01/14 Javascript
JQuery.validate在ie8下不支持的快速解决方法
2016/05/18 Javascript
jquery仿苹果的时间/日期选择效果
2017/03/08 Javascript
JavaScript实现构造json数组的方法分析
2018/08/17 Javascript
JavaScript RegExp 对象用法详解
2019/09/24 Javascript
bootstrap+spring boot实现面包屑导航功能(前端代码)
2019/10/09 Javascript
jQuery zTree如何改变指定节点文本样式
2020/10/16 jQuery
Python闭包之返回函数的函数用法示例
2018/01/27 Python
Python中求对数方法总结
2020/03/10 Python
python爬虫构建代理ip池抓取数据库的示例代码
2020/09/22 Python
matplotlib部件之套索Lasso的使用
2021/02/24 Python
借助HTML5 Canvas来绘制三角形和矩形等多边形的方法
2016/03/14 HTML / CSS
优秀英语专业毕业生求职信
2013/11/23 职场文书
幼儿园教学管理制度
2014/02/04 职场文书
煤矿安全演讲稿
2014/05/09 职场文书
2014年小学辅导员工作总结
2014/12/23 职场文书
2015年安全生产月工作总结
2015/07/27 职场文书
vite+vue3.0+ts+element-plus快速搭建项目的实现
2021/06/24 Vue.js
Docker容器harbor私有仓库部署和管理
2022/08/05 Servers